package oracle

import (
	"net/http"
	"time"

	"github.com/Armur-Ai/Pentest-Swarm-AI/internal/swarm/verify"
)

// DefaultClient returns a plain HTTP client suitable as the base for live
// oracle probes (callers typically wrap it with the campaign's authenticated
// session). The timeout bounds a single probe request; the timing oracle's own
// injected delays stay well under it.
func DefaultClient() *http.Client {
	return &http.Client{Timeout: 30 * time.Second}
}

// Options tunes the live oracle set.
type Options struct {
	OOBWindow    time.Duration // listen window for a callback (default 15s)
	Timing       TimingConfig  // statistical timing parameters
	Similarity   float64       // differential body-similarity threshold (0..1)
	Guard        HostGuard     // optional scope guard; refuses out-of-scope probe hosts
	ProbeTimeout time.Duration // per-probe deadline; 0 = no extra deadline
	Gate         Gate          // optional rate-limit gate; throttles probe requests
}

// BuildLive assembles the external oracles wired to a live target. Each oracle
// is gated on the candidate carrying a matching ProbeSpec, so none fires on a
// finding that shipped no re-proof recipe — the opt-in contract.
//
//   - session == nil → the OOB oracle is skipped (no listener available).
//   - doer == nil    → the differential and timing oracles are skipped.
//
// The returned slice is ready for Corroborator.WithOracles. Ordering puts the
// cheapest-to-decisive first: OOB (one delivery + listen), differential (two
// requests), timing (several requests).
func BuildLive(session OOBSession, doer HTTPDoer, opts Options) []verify.Oracle {
	var oracles []verify.Oracle

	// Scope-guard then rate-limit every probe request (differential, timing,
	// and OOB delivery all go through the doer): nothing leaves the process for
	// an out-of-scope host, and probes obey the campaign's politeness budget.
	doer = RateLimited(Guarded(doer, opts.Guard), opts.Gate)

	if session != nil {
		oob := NewOOB(session, LiveDelivery(doer),
			WithApplies(RequireSpec(verify.ProbeOOB)))
		if opts.OOBWindow > 0 {
			oob = NewOOB(session, LiveDelivery(doer),
				WithApplies(RequireSpec(verify.ProbeOOB)),
				WithWindow(opts.OOBWindow))
		}
		oracles = append(oracles, oob)
	}

	if doer != nil {
		diffOpts := []DiffOption{WithDiffApplies(RequireSpec(verify.ProbeDifferential))}
		if opts.Similarity > 0 {
			diffOpts = append(diffOpts, WithSimilarity(opts.Similarity))
		}
		oracles = append(oracles, NewDifferential(LiveDiffProbe(doer), diffOpts...))

		timeOpts := []TimingOption{WithTimingApplies(RequireSpec(verify.ProbeTiming))}
		if opts.Timing != (TimingConfig{}) {
			timeOpts = append(timeOpts, WithTimingConfig(opts.Timing))
		}
		oracles = append(oracles, NewTiming(LiveTimeProbe(doer), timeOpts...))
	}

	// Bound each probe so one slow target can't stall the campaign.
	if opts.ProbeTimeout > 0 {
		for i, o := range oracles {
			oracles[i] = WithTimeout(o, opts.ProbeTimeout)
		}
	}

	return oracles
}
