package oracle

import (
	"context"
	"fmt"
	"net/http"
)

// HostGuard reports whether a request to host (host[:port]) is permitted. It is
// the oracle layer's blast-radius control: oracles send real, active requests,
// so an out-of-scope host must be refused before anything leaves the process —
// defense in depth even though candidates come from in-scope findings.
type HostGuard func(host string) error

// guardedDoer wraps an HTTPDoer and refuses any request whose host the guard
// rejects. A refused request never touches the network.
type guardedDoer struct {
	inner HTTPDoer
	guard HostGuard
}

func (g guardedDoer) Do(req *http.Request) (*http.Response, error) {
	if g.guard != nil {
		if err := g.guard(req.URL.Host); err != nil {
			return nil, fmt.Errorf("oracle scope guard: %w", err)
		}
	}
	return g.inner.Do(req)
}

// Guarded wraps doer so every probe request is scope-checked. A nil guard or
// nil doer returns the input unchanged, so callers can wrap unconditionally.
func Guarded(doer HTTPDoer, guard HostGuard) HTTPDoer {
	if doer == nil || guard == nil {
		return doer
	}
	return guardedDoer{inner: doer, guard: guard}
}

// Gate blocks until the caller may send the next request, or returns the
// context error. A *ratelimit.Limiter's Take satisfies it, so oracle probes
// obey the campaign's politeness budget without the oracle package importing
// the limiter.
type Gate func(ctx context.Context) error

// rateLimitedDoer takes a token from the gate before each request, so oracle
// probes (the timing oracle fires several in a row) can't burst a target.
type rateLimitedDoer struct {
	inner HTTPDoer
	gate  Gate
}

func (r rateLimitedDoer) Do(req *http.Request) (*http.Response, error) {
	if r.gate != nil {
		if err := r.gate(req.Context()); err != nil {
			return nil, err
		}
	}
	return r.inner.Do(req)
}

// RateLimited wraps doer so each probe waits on gate first. A nil gate or doer
// returns the input unchanged.
func RateLimited(doer HTTPDoer, gate Gate) HTTPDoer {
	if doer == nil || gate == nil {
		return doer
	}
	return rateLimitedDoer{inner: doer, gate: gate}
}
