package oracle

import (
	"context"
	"net/http"
	"net/http/httptest"
	"strconv"
	"strings"
	"testing"
	"time"

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

// economyModel is a cheap-tier model string: its judgments are not auto-trusted,
// so a high-stakes candidate reaches the oracle stage (mirrors verify's tests).
const economyModel = "deepseek/deepseek-v4.1-flash"

// Full live path: BuildLive → Corroborator → a differential oracle probes a real
// (httptest) server → the boundary-crossing finding is PROVEN and promoted, with
// the oracle receipt attached. This is the end-to-end proof that --verify works.
func TestEndToEnd_LiveDifferentialProvesViaCorroborator(t *testing.T) {
	// An IDOR: id=2 (another user) returns data; anything else is forbidden.
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Query().Get("id") == "2" {
			w.WriteHeader(200)
			_, _ = w.Write([]byte(`{"user":"victim","card":"4111-1111-1111-1111"}`))
			return
		}
		w.WriteHeader(403)
		_, _ = w.Write([]byte(`{"error":"forbidden"}`))
	}))
	defer srv.Close()

	spec, err := verify.NewDifferentialSpec(
		"GET /api/user?id="+verify.DefaultMarker+" HTTP/1.1\r\nHost: placeholder\r\n\r\n",
		"", "2", "999999", "victim",
	)
	if err != nil {
		t.Fatal(err)
	}

	// No OOB session; a real HTTP client drives the differential + timing oracles.
	oracles := BuildLive(nil, srv.Client(), Options{})
	corr := verify.New(verify.Policy{MaxOracleProbes: 5}, nil).WithOracles(oracles...)

	cand := verify.Candidate{
		Target:    srv.URL,
		Type:      blackboard.TypeCVEMatch,
		Severity:  pipeline.SeverityCritical, // high-stakes → reaches the oracle stage
		Detail:    "possible IDOR on /api/user/{id}",
		Model:     economyModel,
		Pheromone: 0.4,
		Spec:      spec,
	}

	dec, w, proof := corr.DecideWithProof(context.Background(), cand, nil)
	if dec != verify.Promote {
		t.Fatalf("decision = %v, want Promote (proven IDOR)", dec)
	}
	if !proof.Proven() || proof.Oracle != "differential-response" {
		t.Fatalf("proof = %+v, want proven via differential-response", proof)
	}
	if w < 0.95 {
		t.Errorf("proven weight = %.2f, want >= 0.95", w)
	}
	if corr.OracleProbes() == 0 {
		t.Error("an oracle probe should have been spent")
	}
}

// A finding WITHOUT a recipe falls through the live oracles untouched (they are
// all spec-gated) — no probe is spent, nothing is proven.
func TestEndToEnd_NoSpecNoLiveProbe(t *testing.T) {
	oracles := BuildLive(nil, http.DefaultClient, Options{})
	corr := verify.New(verify.Policy{MaxOracleProbes: 5}, nil).WithOracles(oracles...)

	cand := verify.Candidate{
		Target:    "http://unreachable.invalid",
		Type:      blackboard.TypePotentialSQLI,
		Severity:  pipeline.SeverityCritical,
		Detail:    "blind sqli",
		Model:     economyModel,
		Pheromone: 0.4,
		// no Spec
	}
	dec, _, proof := corr.DecideWithProof(context.Background(), cand, nil)
	if dec != verify.Pass {
		t.Errorf("decision = %v, want Pass (no recipe, no verifier)", dec)
	}
	if proof.Proven() {
		t.Error("nothing should be proven without a recipe")
	}
	if corr.OracleProbes() != 0 {
		t.Errorf("no probe should be spent without a recipe, got %d", corr.OracleProbes())
	}
}

// OOB end-to-end: a candidate with an OOB spec, a (fake) session that observes
// a callback, and a live delivery endpoint → Proven through the Corroborator.
func TestEndToEnd_LiveOOBProvesViaCorroborator(t *testing.T) {
	var delivered bool
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		delivered = true
		w.WriteHeader(200)
	}))
	defer srv.Close()

	sess := &fakeSession{url: "http://abc.oast.fun", token: "abc", hit: true, proof: "dns from 203.0.113.9"}
	oracles := BuildLive(sess, srv.Client(), Options{OOBWindow: time.Second})
	corr := verify.New(verify.Policy{MaxOracleProbes: 5}, nil).WithOracles(oracles...)

	spec, err := verify.NewOOBSpec(
		"GET /fetch?url="+verify.DefaultMarker+" HTTP/1.1\r\nHost: placeholder\r\n\r\n",
		"", "http://"+verify.OOBMarker+"/x",
	)
	if err != nil {
		t.Fatal(err)
	}
	cand := verify.Candidate{
		Target: srv.URL, Type: blackboard.TypeCVEMatch, Severity: pipeline.SeverityCritical,
		Detail: "possible SSRF", Model: economyModel, Pheromone: 0.4, Spec: spec,
	}
	dec, _, proof := corr.DecideWithProof(context.Background(), cand, nil)
	if dec != verify.Promote || proof.Oracle != "oob-callback" || !proof.Proven() {
		t.Fatalf("decision=%v proof=%+v, want promote via oob-callback", dec, proof)
	}
	if !delivered {
		t.Error("the OOB payload should have been delivered to the target")
	}
}

// Timing end-to-end: a candidate with a timing spec against a server that sleeps
// for the injected delay → Proven through the Corroborator.
func TestEndToEnd_LiveTimingProvesViaCorroborator(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		q := r.URL.Query().Get("q")
		if i := strings.Index(q, "SLEEP("); i >= 0 {
			rest := q[i+len("SLEEP("):]
			if j := strings.Index(rest, ")"); j >= 0 {
				if f, err := strconv.ParseFloat(rest[:j], 64); err == nil {
					time.Sleep(time.Duration(f * float64(time.Second)))
				}
			}
		}
		w.WriteHeader(200)
	}))
	defer srv.Close()

	// Small injected delay keeps the test quick but well above local jitter.
	oracles := BuildLive(nil, srv.Client(), Options{
		Timing: TimingConfig{Delay: 300 * time.Millisecond, BaselineSamples: 4, AttackSamples: 3},
	})
	corr := verify.New(verify.Policy{MaxOracleProbes: 5}, nil).WithOracles(oracles...)

	spec, err := verify.NewTimingSpec(
		"GET /?q="+verify.DefaultMarker+" HTTP/1.1\r\nHost: placeholder\r\n\r\n",
		"", "SLEEP("+verify.DelayMarker+")",
	)
	if err != nil {
		t.Fatal(err)
	}
	cand := verify.Candidate{
		Target: srv.URL, Type: blackboard.TypePotentialSQLI, Severity: pipeline.SeverityCritical,
		Detail: "time-based blind sqli", Model: economyModel, Pheromone: 0.4, Spec: spec,
	}
	dec, _, proof := corr.DecideWithProof(context.Background(), cand, nil)
	if dec != verify.Promote || proof.Oracle != "timing" || !proof.Proven() {
		t.Fatalf("decision=%v proof=%+v, want promote via timing", dec, proof)
	}
}
