package app

import (
	"context"
	"encoding/json"
	"errors"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"testing"

	"github.com/stretchr/testify/require"
)

func TestHTTPSingleTicketContextLoaderCollectsLatestAndFreshReplay(t *testing.T) {
	t.Parallel()
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch {
		case r.Method == http.MethodGet && r.URL.Path == "/v1/auto-analysis/issues/APID-29232/latest":
			writeJSON(w, http.StatusOK, map[string]any{"issueKey": "APID-29232", "latestRun": pairedRun("live-run", "old customer", "old internal")})
		case r.Method == http.MethodPost && r.URL.Path == "/v1/tickets/analyze":
			var body map[string]string
			require.NoError(t, json.NewDecoder(r.Body).Decode(&body))
			require.Equal(t, map[string]string{"issueKey": "APID-29232", "mode": "read"}, body)
			writeJSON(w, http.StatusOK, pairedReplay("APID-29232", "replay-run"))
		default:
			http.NotFound(w, r)
		}
	}))
	defer server.Close()

	loader := NewHTTPSingleTicketContextLoader(server.URL)
	contextPacket, err := loader.Load(context.Background(), "APID-29232", "facts disappeared after timeout", 0, "")
	require.NoError(t, err)
	require.Equal(t, "auto-oncall-single-ticket-context-v4", contextPacket.SchemaVersion)
	require.Equal(t, "APID-29232", contextPacket.IssueKey)
	require.Equal(t, "facts disappeared after timeout", contextPacket.Nomination)
	require.Equal(t, http.StatusOK, contextPacket.LatestHTTPStatus)
	require.Equal(t, http.StatusOK, contextPacket.ReplayHTTPStatus)
	require.Contains(t, string(contextPacket.Latest), "live-run")
	require.Contains(t, string(contextPacket.Replay), "replay-run")
	require.Contains(t, string(contextPacket.Replay), "sess_replay_router")
	require.Contains(t, string(contextPacket.Replay), `"humanAnswerMarkdown":"human answer"`)
	require.Contains(t, string(contextPacket.Replay), `"providerQueryAttempts"`)
	require.Contains(t, string(contextPacket.Replay), `"resultStatus":"failed"`)
	require.Contains(t, string(contextPacket.Replay), `"resultStatus":"complete"`)
	require.Equal(t, "different_answer_version", contextPacket.AnswerVersion.JudgeAnswerScope)
	require.Equal(t, "ai_better", contextPacket.AnswerVersion.CurrentJudgeVerdict)
}

func TestHTTPSingleTicketContextLoaderPreservesFailedReplayAsEvidence(t *testing.T) {
	t.Parallel()
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method == http.MethodGet {
			writeJSON(w, http.StatusOK, map[string]any{"issueKey": "APID-29265", "latestRun": pairedRun("old", "old customer", "old internal")})
			return
		}
		writeJSON(w, http.StatusInternalServerError, map[string]any{"status": "error", "code": "diagnostic_finalization_budget_exhausted"})
	}))
	defer server.Close()

	contextPacket, err := NewHTTPSingleTicketContextLoader(server.URL).Load(context.Background(), "APID-29265", "terminal failure", 0, "")
	require.NoError(t, err)
	require.Equal(t, http.StatusInternalServerError, contextPacket.ReplayHTTPStatus)
	require.Contains(t, string(contextPacket.Replay), "finalization_budget_exhausted")
}

func TestHTTPSingleTicketContextLoaderPinsFeedbackAnswerVersion(t *testing.T) {
	t.Parallel()
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch {
		case r.Method == http.MethodGet && r.URL.Path == "/v1/auto-analysis/jobs/91":
			latestRun := pairedRun("feedback-run", "old customer", "old internal")
			latestRun["answerFingerprint"] = "sha256:target"
			writeJSON(w, http.StatusOK, map[string]any{
				"job":       map[string]any{"id": 91, "issueKey": "APID-29232"},
				"latestRun": latestRun,
			})
		case r.Method == http.MethodPost:
			writeJSON(w, http.StatusOK, pairedReplay("APID-29232", "replay-run"))
		default:
			http.NotFound(w, r)
		}
	}))
	defer server.Close()

	loader := NewHTTPSingleTicketContextLoader(server.URL)
	contextPacket, err := loader.Load(context.Background(), "APID-29232", "wrong answer", 91, "sha256:target")
	require.NoError(t, err)
	require.Equal(t, int64(91), contextPacket.SourceJobID)
	require.Equal(t, "sha256:target", contextPacket.AnswerFingerprint)
	require.Equal(t, "sha256:target", contextPacket.AnswerVersion.NominatedFingerprint)
	require.Equal(t, "different_answer_version", contextPacket.AnswerVersion.JudgeAnswerScope)
	require.Contains(t, string(contextPacket.Latest), "feedback-run")

	_, err = loader.Load(context.Background(), "APID-29232", "wrong answer", 91, "sha256:different")
	require.ErrorContains(t, err, "answer version")
}

func TestHTTPSingleTicketContextLoaderBindsJudgeToSameFreshAnswerVersion(t *testing.T) {
	t.Parallel()
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method == http.MethodGet {
			latestRun := pairedRun("feedback-run", "customer", "internal")
			latestRun["answerFingerprint"] = "same-fingerprint"
			writeJSON(w, http.StatusOK, map[string]any{
				"job": map[string]any{"id": 91, "issueKey": "APID-29232"}, "latestRun": latestRun,
			})
			return
		}
		replay := pairedReplay("APID-29232", "replay-run")
		replay["answerFingerprint"] = "same-fingerprint"
		writeJSON(w, http.StatusOK, replay)
	}))
	defer server.Close()

	contextPacket, err := NewHTTPSingleTicketContextLoader(server.URL).Load(
		context.Background(), "APID-29232", "wrong answer", 91, "same-fingerprint",
	)
	require.NoError(t, err)
	require.Equal(t, "same_answer_version", contextPacket.AnswerVersion.JudgeAnswerScope)
	require.Equal(t, "ai_better", contextPacket.AnswerVersion.CurrentJudgeVerdict)
}

func TestHTTPSingleTicketContextLoaderRejectsSuccessfulReplayWithoutPairedMessages(t *testing.T) {
	t.Parallel()
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method == http.MethodGet {
			writeJSON(w, http.StatusOK, map[string]any{"issueKey": "APID-29232", "latestRun": pairedRun("old", "old customer", "old internal")})
			return
		}
		writeJSON(w, http.StatusOK, map[string]any{
			"issueKey":  "APID-29232",
			"execution": map[string]any{"responseGeneration": map[string]any{"status": "complete"}},
			"result": map[string]any{
				"decision": "internal_only", "customerReplyMarkdown": "customer only",
				"clarificationSimulation": map[string]any{"required": false},
			},
		})
	}))
	defer server.Close()

	_, err := NewHTTPSingleTicketContextLoader(server.URL).Load(context.Background(), "APID-29232", "missing handoff", 0, "")
	require.ErrorContains(t, err, "customer and internal messages are both required")
}

func TestHTTPSingleTicketContextLoaderRejectsEvaluatedReplayWithoutHumanAnswer(t *testing.T) {
	t.Parallel()
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method == http.MethodGet {
			writeJSON(w, http.StatusOK, map[string]any{"issueKey": "APID-29232", "latestRun": pairedRun("old", "old customer", "old internal")})
			return
		}
		replay := pairedReplay("APID-29232", "fresh")
		replay["evaluation"].(map[string]any)["humanAnswerMarkdown"] = nil
		writeJSON(w, http.StatusOK, replay)
	}))
	defer server.Close()

	_, err := NewHTTPSingleTicketContextLoader(server.URL).Load(context.Background(), "APID-29232", "review the wrong answer", 0, "")
	require.ErrorContains(t, err, "missing evaluated human answer")
}

func TestHTTPSingleTicketContextLoaderReadsLegacyMessageNamesDuringRollout(t *testing.T) {
	t.Parallel()
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		legacy := map[string]any{
			"decision": "internal_only", "commentDraftMarkdown": "legacy customer",
			"replayContextMarkdown":   "legacy internal",
			"clarificationSimulation": map[string]any{"required": false},
			"execution":               map[string]any{"responseGeneration": map[string]any{"status": "complete"}},
		}
		if r.Method == http.MethodGet {
			writeJSON(w, http.StatusOK, map[string]any{"issueKey": "APID-29232", "latestRun": legacy})
			return
		}
		writeJSON(w, http.StatusOK, map[string]any{
			"issueKey":  "APID-29232",
			"execution": legacy["execution"],
			"result":    legacy,
			"evaluation": map[string]any{
				"status": "not_comparable", "verdict": "not_compared", "humanAnswerMarkdown": nil,
			},
		})
	}))
	defer server.Close()

	loaded, err := NewHTTPSingleTicketContextLoader(server.URL).Load(context.Background(), "APID-29232", "rollout compatibility", 0, "")
	require.NoError(t, err)
	require.Contains(t, string(loaded.Latest), `"customerReplyMarkdown":"legacy customer"`)
	require.Contains(t, string(loaded.Latest), `"internalHandoffMarkdown":"legacy internal"`)
	require.NotContains(t, string(loaded.Latest), "commentDraftMarkdown")
	require.NotContains(t, string(loaded.Latest), "replayContextMarkdown")
	require.Contains(t, string(loaded.Replay), `"customerReplyMarkdown":"legacy customer"`)
	require.Contains(t, string(loaded.Replay), `"internalHandoffMarkdown":"legacy internal"`)
	require.NotContains(t, string(loaded.Replay), "commentDraftMarkdown")
	require.NotContains(t, string(loaded.Replay), "replayContextMarkdown")
}

func TestSingleTicketInvestigationAdmitsOnlyCompleteDeterministicDefect(t *testing.T) {
	t.Parallel()
	result := deterministicInvestigation()
	payload, err := json.Marshal(result)
	require.NoError(t, err)
	parsed, err := parseSingleTicketInvestigation(payload, "APID-29232")
	require.NoError(t, err)
	selected := parsed.selectedRepairFinding()
	require.NotNil(t, selected)
	require.Equal(t, "deterministic_engineering", selected.RepairDecision.Repairability)
	require.Equal(t, "evidence_handoff", selected.FirstBrokenBoundary.Stage)

	result.Findings[0].RepairDecision.AcceptanceCriterion = nil
	payload, err = json.Marshal(result)
	require.NoError(t, err)
	_, err = parseSingleTicketInvestigation(payload, "APID-29232")
	require.ErrorContains(t, err, "repair gate is incomplete")
}

func TestSingleTicketInvestigationRequiresIndependentAnswerReview(t *testing.T) {
	t.Parallel()
	result := deterministicInvestigation()
	result.AnswerReview = SingleTicketAnswerReview{}
	payload, err := json.Marshal(result)
	require.NoError(t, err)

	_, err = parseSingleTicketInvestigation(payload, "APID-29232")
	require.ErrorContains(t, err, "answer review is invalid")
}

func TestSingleTicketInvestigationUsesControllerAnswerVersionInsteadOfModelOutput(t *testing.T) {
	t.Parallel()
	result := noProblemInvestigation()
	controllerVersion := result.AnswerVersion
	result.AnswerVersion = SingleTicketAnswerVersion{}
	payload, err := json.Marshal(result)
	require.NoError(t, err)

	parsed, err := parseSingleTicketInvestigation(payload, "APID-29232", controllerVersion)
	require.NoError(t, err)
	require.Equal(t, controllerVersion, parsed.AnswerVersion)
}

func TestSingleTicketInvestigationKeepsTCID13107CurrentJudgeVerdictSeparateFromHistoricalFeedback(t *testing.T) {
	t.Parallel()
	result := focusInvestigation()
	result.IssueKey = "TCID-13107"
	result.AnswerReview.NominatedProblemStatus = "not_reproduced"
	payload, err := json.Marshal(result)
	require.NoError(t, err)

	parsed, err := parseSingleTicketInvestigation(payload, "TCID-13107", result.AnswerVersion)
	require.NoError(t, err)
	require.Equal(t, "different_answer_version", parsed.AnswerVersion.JudgeAnswerScope)
	require.Equal(t, "ai_better", parsed.AnswerVersion.CurrentJudgeVerdict)
	require.Equal(t, "wrong", parsed.AnswerReview.NominatedAnswerVerdict)
	require.Equal(t, "not_reproduced", parsed.AnswerReview.NominatedProblemStatus)
	require.Equal(t, "focus_candidate", parsed.Classification)
	require.Contains(t, singleTicketOutcomeMessage(parsed, ""), "当前答案 Judge：`ai_better`（`different_answer_version`）")
	require.NotContains(t, singleTicketOutcomeMessage(parsed, ""), "conflicted")
}

func TestSingleTicketInvestigationRetainsSameVersionFeedbackReview(t *testing.T) {
	t.Parallel()
	result := focusInvestigation()
	result.AnswerVersion.CurrentFingerprint = result.AnswerVersion.NominatedFingerprint
	result.AnswerVersion.JudgeAnswerScope = "same_answer_version"
	result.AnswerVersion.CurrentJudgeVerdict = "human_better"
	payload, err := json.Marshal(result)
	require.NoError(t, err)

	parsed, err := parseSingleTicketInvestigation(payload, "APID-29232", result.AnswerVersion)
	require.NoError(t, err)
	require.Equal(t, "same_answer_version", parsed.AnswerVersion.JudgeAnswerScope)
	require.Equal(t, "human_better", parsed.AnswerVersion.CurrentJudgeVerdict)
	require.Equal(t, "reproduced", parsed.AnswerReview.NominatedProblemStatus)
}

func TestSingleTicketInvestigationRoutesSemanticGapToFocus(t *testing.T) {
	t.Parallel()
	result := focusInvestigation()
	payload, err := json.Marshal(result)
	require.NoError(t, err)
	parsed, err := parseSingleTicketInvestigation(payload, "APID-29232")
	require.NoError(t, err)
	require.Equal(t, "focus_candidate", parsed.Classification)

	result.Findings[0].RepairDecision.Repairability = "deterministic_engineering"
	payload, err = json.Marshal(result)
	require.NoError(t, err)
	_, err = parseSingleTicketInvestigation(payload, "APID-29232")
	require.Error(t, err)
}

func TestSingleTicketInvestigationSelectsBoundedKnowledgeWithoutSuppressingFocusFinding(t *testing.T) {
	t.Parallel()
	result := deterministicInvestigation()
	result.Findings[0].ID = "private-endpoint-region-knowledge"
	result.Findings[0].Kind = "knowledge_contract"
	result.Findings[0].AuthorityRefs = []string{"https://docs.pingcap.com/tidbcloud/private-endpoint-connections"}
	result.Findings = append(result.Findings, focusInvestigation().Findings[0])
	selected := result.Findings[0].ID
	result.SelectedRepairFindingID = &selected

	payload, err := json.Marshal(result)
	require.NoError(t, err)
	parsed, err := parseSingleTicketInvestigation(payload, "APID-29232")
	require.NoError(t, err)
	require.Len(t, parsed.Findings, 2)
	require.Equal(t, "knowledge_contract", parsed.selectedRepairFinding().Kind)
	require.Equal(t, "diagnostic_strategy", parsed.Findings[1].Kind)
}

func TestSingleTicketInvestigationRejectsUnverifiedKnowledgeAndExternalTransientRepair(t *testing.T) {
	t.Parallel()
	result := deterministicInvestigation()
	result.Findings[0].Kind = "knowledge_contract"
	payload, err := json.Marshal(result)
	require.NoError(t, err)
	_, err = parseSingleTicketInvestigation(payload, "APID-29232")
	require.ErrorContains(t, err, "official authority")

	result.Findings[0].AuthorityRefs = []string{"https://example.com/unverified"}
	payload, err = json.Marshal(result)
	require.NoError(t, err)
	_, err = parseSingleTicketInvestigation(payload, "APID-29232")
	require.ErrorContains(t, err, "authority")

	result.Findings[0].Kind = "external_transient"
	result.Findings[0].AuthorityRefs = nil
	payload, err = json.Marshal(result)
	require.NoError(t, err)
	_, err = parseSingleTicketInvestigation(payload, "APID-29232")
	require.ErrorContains(t, err, "cannot enter automatic repair")
}

func TestSingleTicketInvestigationCorrectsCurrentFocusContractOnce(t *testing.T) {
	t.Parallel()
	result := focusInvestigation()
	result.ClassificationReason = "The current answer has a semantic gap that needs more cases."
	result.Findings[0].Kind = "provider_query_contract"
	invalidPayload, err := json.Marshal(result)
	require.NoError(t, err)

	corrected := result
	corrected.Findings[0].Kind = "diagnostic_strategy"
	correctedPayload, err := json.Marshal(corrected)
	require.NoError(t, err)

	corrections := 0
	parsed, raw, err := parseSingleTicketInvestigationWithCorrection(
		string(invalidPayload),
		"APID-29232",
		result.AnswerVersion,
		func(prompt string) (string, error) {
			corrections++
			require.Contains(t, prompt, "focus candidate requires an unresolved diagnostic-strategy finding")
			require.Contains(t, prompt, "current main plus the fresh replay")
			require.Contains(t, prompt, "Do not investigate again")
			return string(correctedPayload), nil
		},
	)
	require.NoError(t, err)
	require.Equal(t, 1, corrections)
	require.Equal(t, "focus_candidate", parsed.Classification)
	require.Nil(t, parsed.FirstBrokenBoundary)
	require.Equal(t, string(correctedPayload), raw)
}

func TestSingleTicketInvestigationCorrectsHistoricalNoProblemFieldsOnce(t *testing.T) {
	t.Parallel()
	result := noProblemInvestigation()
	result.ClassificationReason = "The historical answer was incomplete, but current replay no longer reproduces it."
	result.Findings = []SingleTicketFinding{{
		ID: "historical-answer", Kind: "historical_only", Summary: "Only the nominated answer was incomplete.",
		DiagnosticImpact: SingleTicketImpact{Kind: "none", Summary: "Current replay is unaffected."},
		RepairDecision:   SingleTicketRepairDecision{Repairability: "not_applicable", Reason: "Current replay does not reproduce it."},
	}}
	invalidPayload, err := json.Marshal(result)
	require.NoError(t, err)

	corrected := result
	corrected.Findings = []SingleTicketFinding{}
	correctedPayload, err := json.Marshal(corrected)
	require.NoError(t, err)

	corrections := 0
	parsed, _, err := parseSingleTicketInvestigationWithCorrection(
		string(invalidPayload),
		"APID-29232",
		result.AnswerVersion,
		func(prompt string) (string, error) {
			corrections++
			require.Contains(t, prompt, "every non-historical finding")
			return string(correctedPayload), nil
		},
	)
	require.NoError(t, err)
	require.Equal(t, 1, corrections)
	require.Equal(t, "no_problem", parsed.Classification)
	require.Empty(t, parsed.Findings)
}

func TestSingleTicketInvestigationCorrectionIsBoundedToOneAttempt(t *testing.T) {
	t.Parallel()
	result := deterministicInvestigation()
	result.Classification = "no_problem"
	invalidPayload, err := json.Marshal(result)
	require.NoError(t, err)

	corrections := 0
	_, _, err = parseSingleTicketInvestigationWithCorrection(
		string(invalidPayload),
		"APID-29232",
		result.AnswerVersion,
		func(string) (string, error) {
			corrections++
			return string(invalidPayload), nil
		},
	)
	require.ErrorContains(t, err, "invalid after one attempt")
	require.Equal(t, 1, corrections)
}

func TestSingleTicketInvestigationDoesNotCorrectStructuralFailure(t *testing.T) {
	t.Parallel()
	result := deterministicInvestigation()
	result.IssueKey = "APID-99999"
	payload, err := json.Marshal(result)
	require.NoError(t, err)

	called := false
	_, _, err = parseSingleTicketInvestigationWithCorrection(
		string(payload),
		"APID-29232",
		result.AnswerVersion,
		func(string) (string, error) {
			called = true
			return "", nil
		},
	)
	require.ErrorContains(t, err, "identity or classification is invalid")
	require.False(t, called)
}

func TestSingleTicketInvestigationRequiresNoProblemToBeInternallyClean(t *testing.T) {
	t.Parallel()
	result := noProblemInvestigation()
	payload, err := json.Marshal(result)
	require.NoError(t, err)
	_, err = parseSingleTicketInvestigation(payload, "APID-29232")
	require.NoError(t, err)

	result.Findings = []SingleTicketFinding{{
		ID: "stale", Kind: "historical_only", Summary: "A stale finding remained.",
		DiagnosticImpact: SingleTicketImpact{Kind: "none", Summary: "No current impact."},
		RepairDecision:   SingleTicketRepairDecision{Repairability: "not_applicable", Reason: "No current repair."},
	}}
	payload, err = json.Marshal(result)
	require.NoError(t, err)
	_, err = parseSingleTicketInvestigation(payload, "APID-29232")
	require.ErrorContains(t, err, "no-problem")
}

func TestSingleTicketPromptKeepsExplorationOpenAndRepairNarrow(t *testing.T) {
	t.Parallel()
	prompt := singleTicketInvestigationPrompt(Job{
		IssueKey: "APID-29232",
		Request:  "APID-29232 investigate the observed bad answer",
	})
	require.Contains(t, prompt, "diagnose real duration, orchestration, evidence, and answer-quality failures")
	require.Contains(t, prompt, "fresh replay non-2xx response is itself a current runtime artifact")
	require.Contains(t, prompt, "compare any available AOS process or Pod lifecycle evidence with the replay window")
	require.Contains(t, prompt, "an AOS restart or rollout that replaced the caller can terminate its long HTTP request without proving an LLM or Agent9 failure")
	require.Contains(t, prompt, "name the missing AOS restart/rollout discriminator")
	require.Contains(t, prompt, "a nearby passing test does not prove that the failing boundary is unknown")
	require.Contains(t, prompt, "Select at most one finding")
	require.Contains(t, prompt, "stable product knowledge")
	require.Contains(t, prompt, "local deterministic Provider query")
	require.Contains(t, prompt, "safe fallback does not erase")
	require.Contains(t, prompt, "Use historical fixes only after independently locating the boundary")
	require.Contains(t, prompt, "describe current main plus the fresh replay")
	require.Contains(t, prompt, "historical_only")
	require.Contains(t, prompt, "focus_candidate")
	require.Contains(t, prompt, "customerReplyMarkdown")
	require.Contains(t, prompt, "internalHandoffMarkdown")
	require.Contains(t, prompt, "decision=human_required")
	require.Contains(t, prompt, "complete answerReview independently")
	require.Contains(t, prompt, "full same-stage human answer")
	require.Contains(t, prompt, "currentJudgeVerdict is the verdict for the fresh replay answer")
	require.Contains(t, prompt, "must not determine the current answer verdict")
	require.Contains(t, prompt, "nominatedProblemStatus")
	require.Contains(t, prompt, "do not reproduce answerVersion in your JSON")
	require.Contains(t, prompt, "Every Auto-oncall ticket remains owned by a Support person for follow-up")
	require.Contains(t, prompt, "does not prove that Support cannot perform the named manual checks")
	require.Contains(t, prompt, "Do not create a finding solely because that normal human follow-up is not backed by an executed Provider call")
	require.Contains(t, prompt, "providerQueryAttempts")
	require.Contains(t, prompt, "failureCodes are only the deduplicated final-status and candidate-trigger summary")
	require.Contains(t, prompt, "corrected later in the same run")
	require.Contains(t, prompt, "classificationReason as one to three plain, self-contained Chinese sentences")
	require.Contains(t, prompt, "这次回答")
	require.Contains(t, prompt, "新鲜回放")
	require.NotContains(t, prompt, "handoff omitted numeric facts")
}

func TestSingleTicketValidationRepairPromptIsBoundedAndUsesExactFailure(t *testing.T) {
	t.Parallel()
	prompt := singleTicketValidationRepairPrompt("ST-2914", errors.New("not ok: expected inventory omitted new-topic"))
	require.Contains(t, prompt, "not ok: expected inventory omitted new-topic")
	require.Contains(t, prompt, "once in the current worktree")
	require.Contains(t, prompt, "directly affected test, snapshot, expected inventory")
	require.Contains(t, prompt, "Do not add a new product behavior")
	require.Contains(t, prompt, "untouched origin/main")
}

func TestSingleTicketSchemaCanBeSerialized(t *testing.T) {
	t.Parallel()
	payload, err := json.Marshal(singleTicketInvestigationSchema())
	require.NoError(t, err)
	require.Contains(t, string(payload), singleTicketSchemaVersion)
	require.Contains(t, string(payload), "deterministic_engineering")
}

func TestSingleTicketImplementationTargetsAllowOnlyExactNewProductKnowledgeTopic(t *testing.T) {
	t.Parallel()
	root := t.TempDir()
	target := filepath.Join(root, "auto-oncall-services", "src", "runner.ts")
	require.NoError(t, os.MkdirAll(filepath.Dir(target), 0o750))
	require.NoError(t, os.MkdirAll(filepath.Join(root, "auto-oncall-services", "product-knowledge", "topics"), 0o750))
	require.NoError(t, os.WriteFile(target, []byte("export {};\n"), 0o600))
	require.NoError(t, validateImplementationTargets(root, []string{"auto-oncall-services/src/runner.ts"}))
	require.NoError(t, validateImplementationTargets(root, []string{"auto-oncall-services/product-knowledge/topics/private-endpoint-region.md"}))
	require.Error(t, validateImplementationTargets(root, []string{"auto-oncall-services/src/missing.ts"}))
	require.Error(t, validateImplementationTargets(root, []string{"auto-oncall-services/product-knowledge/new-index.ndjson"}))
	require.Error(t, validateImplementationTargets(root, []string{"auto-oncall-services/product-knowledge/topics/nested/topic.md"}))
}

func TestSingleTicketInvestigationCorrectsAgent9ClinicProviderOwnerOnce(t *testing.T) {
	t.Parallel()
	result := deterministicInvestigation()
	result.Findings[0].FirstBrokenBoundary.Stage = "provider_execution"
	result.Findings[0].RepairDecision.ImplementationTargets = []string{
		"auto-oncall-services/src/clinic-evidence-batch.ts",
		"auto-oncall-services/test/clinic-evidence.test.ts",
	}
	invalidPayload, err := json.Marshal(result)
	require.NoError(t, err)

	corrected := result
	corrected.Findings[0].RepairDecision.ImplementationTargets = []string{
		"auto-oncall-services/tools/agent9-auto-oncall/managed-clinic/index.mjs",
		"auto-oncall-services/test/agent9-managed-tool-packages.test.ts",
	}
	correctedPayload, err := json.Marshal(corrected)
	require.NoError(t, err)
	replay, err := json.Marshal(pairedReplay("TCID-13127", "fresh"))
	require.NoError(t, err)

	corrections := 0
	parsed, _, err := parseSingleTicketInvestigationWithCorrection(
		string(invalidPayload),
		"APID-29232",
		result.AnswerVersion,
		func(prompt string) (string, error) {
			corrections++
			require.Contains(t, prompt, "fresh replay used Agent9 Managed Clinic")
			return string(correctedPayload), nil
		},
		func(investigation *SingleTicketInvestigation) error {
			return validateSingleTicketRuntimeOwner(replay, investigation)
		},
	)
	require.NoError(t, err)
	require.Equal(t, 1, corrections)
	require.Equal(t, corrected.Findings[0].RepairDecision.ImplementationTargets, parsed.Findings[0].RepairDecision.ImplementationTargets)
}

func TestSingleTicketRuntimeOwnerAllowsLocalClinicCompatibilityExecutor(t *testing.T) {
	t.Parallel()
	result := deterministicInvestigation()
	result.Findings[0].FirstBrokenBoundary.Stage = "provider_execution"
	result.Findings[0].RepairDecision.ImplementationTargets = []string{"auto-oncall-services/src/clinic-evidence-batch.ts"}
	replayValue := pairedReplay("TCID-13127", "fresh")
	replayValue["runtimeTrace"].(map[string]any)["backend"] = "codex-sdk"
	replay, err := json.Marshal(replayValue)
	require.NoError(t, err)
	require.NoError(t, validateSingleTicketRuntimeOwner(replay, &result))
}

func deterministicInvestigation() SingleTicketInvestigation {
	violated := "A validated provider handoff must retain facts available to its parent."
	reproduction := "Compare the completed provider artifact with the parent-visible handoff."
	owner := "scenario handoff materialization"
	acceptance := "The same provider result produces a handoff containing the bounded facts."
	result := SingleTicketInvestigation{
		SchemaVersion:        singleTicketSchemaVersion,
		IssueKey:             "APID-29232",
		Classification:       "engineering_defect",
		ClassificationReason: "The provider completed, but the parent-readable packet lost its facts.",
		Findings: []SingleTicketFinding{{
			ID: "provider-handoff", Kind: "provider_query_contract", Summary: "The provider handoff lost materialized facts.",
			FirstBrokenBoundary: &SingleTicketBrokenBoundary{
				Stage: "evidence_handoff", Layer: "consumable", Producer: "provider materializer", Consumer: "parent judgment",
				Expected: "bounded facts are present", Observed: "only an artifact ref is present",
				EvidenceRefs: []string{singleTicketContextFile, "auto-oncall-services/src/runner.ts:1"},
			},
			DiagnosticImpact: SingleTicketImpact{
				Kind: "unnecessary_refinement", Summary: "The parent repeated acquisition because facts were absent.",
				EvidenceRefs: []string{singleTicketContextFile},
			},
			RepairDecision: SingleTicketRepairDecision{
				Repairability: "deterministic_engineering", Reason: "One owner and pass/fail contract are proven.",
				ViolatedContract: &violated, Reproduction: &reproduction, OwnerBoundary: &owner,
				AcceptanceCriterion:   &acceptance,
				ImplementationTargets: []string{"auto-oncall-services/src/runner.ts", "auto-oncall-services/test/runner.test.ts"},
			},
		}},
		Uncertainties: []string{},
	}
	result.AnswerReview = testAnswerReview()
	result.AnswerVersion = testAnswerVersion()
	selected := "provider-handoff"
	result.SelectedRepairFindingID = &selected
	result.Chain.Produced = boundaryState("established")
	result.Chain.Consumable = boundaryState("broken")
	result.Chain.Consumed = boundaryState("not_reached")
	result.Chain.Completed = boundaryState("broken")
	return result
}

func focusInvestigation() SingleTicketInvestigation {
	result := SingleTicketInvestigation{
		SchemaVersion: singleTicketSchemaVersion, IssueKey: "APID-29232", Classification: "focus_candidate",
		ClassificationReason: "The answer needs a reusable diagnostic strategy.",
		Findings: []SingleTicketFinding{{
			ID: "diagnostic-policy", Kind: "diagnostic_strategy", Summary: "One case cannot establish the diagnosis rule.",
			DiagnosticImpact: SingleTicketImpact{Kind: "analysis_quality_gap", Summary: "The answer remains ambiguous.", EvidenceRefs: []string{singleTicketContextFile}},
			RepairDecision:   SingleTicketRepairDecision{Repairability: "requires_cross_case_evaluation", Reason: "More cases are required."},
		}},
		Uncertainties: []string{},
	}
	result.AnswerReview = testAnswerReview()
	result.AnswerVersion = testAnswerVersion()
	result.Chain.Produced = boundaryState("established")
	result.Chain.Consumable = boundaryState("established")
	result.Chain.Consumed = boundaryState("established")
	result.Chain.Completed = boundaryState("established")
	return result
}

func noProblemInvestigation() SingleTicketInvestigation {
	result := SingleTicketInvestigation{
		SchemaVersion: singleTicketSchemaVersion, IssueKey: "APID-29232", Classification: "no_problem",
		ClassificationReason: "Current replay does not reproduce the nomination.", Findings: []SingleTicketFinding{}, Uncertainties: []string{},
	}
	result.AnswerReview = testAnswerReview()
	result.AnswerReview.NominatedProblemStatus = "not_reproduced"
	result.AnswerVersion = testAnswerVersion()
	result.Chain.Produced = boundaryState("established")
	result.Chain.Consumable = boundaryState("established")
	result.Chain.Consumed = boundaryState("established")
	result.Chain.Completed = boundaryState("established")
	return result
}

func boundaryState(status string) SingleTicketBoundaryState {
	return SingleTicketBoundaryState{Status: status, Summary: status + " from context", EvidenceRefs: []string{singleTicketContextFile}}
}

func pairedRun(runID, customer, internal string) map[string]any {
	return map[string]any{
		"runId": runID, "decision": "internal_only", "answerFingerprint": "old-fingerprint",
		"customerReplyMarkdown": customer, "internalHandoffMarkdown": internal,
		"clarificationSimulation": map[string]any{"required": false},
		"execution":               map[string]any{"responseGeneration": map[string]any{"status": "complete"}},
	}
}

func pairedReplay(issueKey, runID string) map[string]any {
	return map[string]any{
		"issueKey": issueKey, "runId": runID, "answerFingerprint": "fresh-fingerprint",
		"execution": map[string]any{
			"responseGeneration": map[string]any{"status": "complete"},
			"evidenceAcquisition": map[string]any{
				"status": "complete", "failureCodes": []string{},
				"providerQueryAttempts": []map[string]any{{
					"provider": "clinic", "scenarioId": "query-latency", "batchRound": 1,
					"queryId": "digest-history", "operation": "sql_query", "attempt": 1,
					"resultStatus": "failed", "errorCode": "clinic_http_400",
				}, {
					"provider": "clinic", "scenarioId": "query-latency", "batchRound": 1,
					"queryId": "digest-history", "operation": "sql_query", "attempt": 2,
					"resultStatus": "complete", "errorCode": nil,
				}},
			},
		},
		"runtimeTrace": map[string]any{
			"schemaVersion": "auto-oncall-agent-runtime-trace-v1",
			"backend":       "agent9",
			"calls": []map[string]any{{
				"callName": "scenario_router_assignment",
				"outcome":  "succeeded",
				"threadId": "sess_replay_router",
			}},
		},
		"result": map[string]any{
			"decision": "internal_only", "customerReplyMarkdown": "fresh customer",
			"internalHandoffMarkdown": "fresh internal",
			"clarificationSimulation": map[string]any{"required": false},
		},
		"evaluation": map[string]any{
			"status": "evaluated", "verdict": "ai_better", "humanAnswerMarkdown": "human answer",
			"referenceComments": []map[string]any{{"id": "human-1", "bodyDigest": "sha256:human"}},
		},
	}
}

func testAnswerReview() SingleTicketAnswerReview {
	available := func(summary, ref string) SingleTicketReviewSubject {
		return SingleTicketReviewSubject{Status: "available", Summary: summary, EvidenceRefs: []string{ref}}
	}
	return SingleTicketAnswerReview{
		NominatedAnswerVerdict: "wrong",
		NominatedProblemStatus: "reproduced",
		OriginalAIAnswer:       available("The original answer missed the material conclusion.", singleTicketContextFile+"#latest.latestRun"),
		HumanAnswer:            available("The human answer supplied the material conclusion.", singleTicketContextFile+"#replay.evaluation.humanAnswerMarkdown"),
		HumanFeedback:          available("The feedback marks the original answer wrong.", singleTicketContextFile+"#nomination"),
		FreshJudge:             available("The Judge preferred a different fresh answer.", singleTicketContextFile+"#replay.evaluation"),
		IndependentConclusion:  "The historical problem is reproduced independently of the fresh Judge verdict.",
		EvidenceRefs:           []string{singleTicketContextFile},
	}
}

func testAnswerVersion() SingleTicketAnswerVersion {
	return SingleTicketAnswerVersion{
		NominatedFingerprint: "old-fingerprint",
		CurrentFingerprint:   "fresh-fingerprint",
		JudgeAnswerScope:     "different_answer_version",
		CurrentJudgeStatus:   "evaluated",
		CurrentJudgeVerdict:  "ai_better",
	}
}
