package oracle

import (
	"context"
	"fmt"
	"strconv"
	"strings"
	"time"

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

// This file adapts the generic oracle callbacks (Delivery, DiffProbe,
// TimeProbe) to a live target, driving the raw-HTTP primitive from each
// candidate's ProbeSpec. The oracles themselves stay transport-agnostic; these
// constructors are the only place that actually sends requests.

// RequireSpec returns an Applies predicate that gates an oracle on the
// candidate carrying a valid ProbeSpec of the given class — so a live oracle
// only fires on findings that shipped a re-proof recipe.
func RequireSpec(class verify.ProbeClass) func(verify.Candidate) bool {
	return func(c verify.Candidate) bool { return c.CanProbe(class) }
}

// LiveDelivery builds the OOB Delivery: it injects the reserved callback URL
// into the spec's OOB payload, places that at the injection marker, and sends
// the request. It does not inspect the response — the callback is the signal.
func LiveDelivery(doer HTTPDoer) Delivery {
	return func(ctx context.Context, c verify.Candidate, oobURL string) error {
		if !c.CanProbe(verify.ProbeOOB) {
			return fmt.Errorf("oob delivery: candidate carries no valid oob spec")
		}
		payload := strings.ReplaceAll(c.Spec.OOBTemplate, verify.OOBMarker, oobURL)
		raw := c.Spec.Inject(payload)
		if _, _, _, err := sendRaw(ctx, doer, c.Target, raw); err != nil {
			return fmt.Errorf("oob delivery: %w", err)
		}
		return nil
	}
}

// LiveDiffProbe builds the differential DiffProbe: it sends the attack request
// and the negative-control request and returns both responses for comparison.
func LiveDiffProbe(doer HTTPDoer) DiffProbe {
	return func(ctx context.Context, c verify.Candidate) (Response, Response, error) {
		if !c.CanProbe(verify.ProbeDifferential) {
			return Response{}, Response{}, fmt.Errorf("differential: candidate carries no valid differential spec")
		}
		attack, err := sendResp(ctx, doer, c.Target, c.Spec.Inject(c.Spec.AttackPayload))
		if err != nil {
			return Response{}, Response{}, fmt.Errorf("differential attack: %w", err)
		}
		control, err := sendResp(ctx, doer, c.Target, c.Spec.Inject(c.Spec.ControlPayload))
		if err != nil {
			return Response{}, Response{}, fmt.Errorf("differential control: %w", err)
		}
		return attack, control, nil
	}
}

// LiveTimeProbe builds the timing TimeProbe: it injects the requested delay (in
// seconds) into the spec's delay payload and returns the observed latency. A
// zero delay yields a baseline request (e.g. SLEEP(0)).
func LiveTimeProbe(doer HTTPDoer) TimeProbe {
	return func(ctx context.Context, c verify.Candidate, delay time.Duration) (time.Duration, error) {
		if !c.CanProbe(verify.ProbeTiming) {
			return 0, fmt.Errorf("timing: candidate carries no valid timing spec")
		}
		secs := strconv.FormatFloat(delay.Seconds(), 'g', -1, 64)
		payload := strings.ReplaceAll(c.Spec.DelayTemplate, verify.DelayMarker, secs)
		_, _, lat, err := sendRaw(ctx, doer, c.Target, c.Spec.Inject(payload))
		if err != nil {
			return 0, fmt.Errorf("timing probe: %w", err)
		}
		return lat, nil
	}
}

// LiveRedirectProbe builds the open-redirect RedirectProbe: it injects the
// attack URL at the marker and returns the response status + Location header
// without following the redirect (doer must be a non-redirect-following client).
func LiveRedirectProbe(doer HTTPDoer) RedirectProbe {
	return func(ctx context.Context, c verify.Candidate) (int, string, error) {
		if c.Spec == nil {
			return 0, "", fmt.Errorf("open-redirect: candidate carries no spec")
		}
		raw := c.Spec.Inject(c.Spec.AttackPayload)
		status, header, err := sendRawHeaders(ctx, doer, c.Target, raw)
		if err != nil {
			return 0, "", fmt.Errorf("open-redirect probe: %w", err)
		}
		return status, header.Get("Location"), nil
	}
}

// sendResp is sendRaw reduced to the Response the differential oracle compares.
func sendResp(ctx context.Context, doer HTTPDoer, target, raw string) (Response, error) {
	status, body, _, err := sendRaw(ctx, doer, target, raw)
	if err != nil {
		return Response{}, err
	}
	return Response{Status: status, Body: body}, nil
}
