package report

import (
	"path/filepath"
	"testing"

	"github.com/Clyra-AI/wrkr/core/attribution"
	"github.com/Clyra-AI/wrkr/core/ingest"
	"github.com/Clyra-AI/wrkr/core/risk"
	"github.com/Clyra-AI/wrkr/core/source"
	"github.com/Clyra-AI/wrkr/core/state"
)

func TestRuntimeSessionSidecarFeedsRuntimeEvidencePacketsAndBOM(t *testing.T) {
	t.Parallel()

	root := t.TempDir()
	statePath := filepath.Join(root, "state.json")
	snapshot := state.Snapshot{
		Target: source.Target{Mode: "path"},
		RiskReport: &risk.Report{
			ActionPaths: []risk.ActionPath{{
				PathID:               "apc-runtime-1",
				AgentID:              "wrkr:codex-release:acme",
				Org:                  "acme",
				Repo:                 "acme/payments",
				ToolType:             "compiled_action",
				Location:             ".github/workflows/release.yml",
				ActionClasses:        []string{"deploy", "write"},
				WriteCapable:         true,
				CredentialAccess:     true,
				RecommendedAction:    "control",
				PolicyCoverageStatus: risk.PolicyCoverageStatusNone,
				IntroducedBy: &attribution.Result{
					Reference:   "pr/42",
					ChangedFile: "cmd/release.go",
				},
			}},
		},
	}
	if err := state.Save(statePath, snapshot); err != nil {
		t.Fatalf("save state: %v", err)
	}
	if err := ingest.SaveSessionBundle(ingest.DefaultSessionPath(statePath), ingest.SessionBundle{
		GeneratedAt: "2026-05-27T14:00:00Z",
		Sessions: []ingest.SessionRecord{{
			Provider:             ingest.SessionProviderCodex,
			SessionID:            "sess-1",
			AgentID:              "wrkr:codex-release:acme",
			Repo:                 "acme/payments",
			Workflow:             ".github/workflows/release.yml",
			PullRequestRef:       "pr/42",
			ChangedFiles:         []string{"cmd/release.go"},
			Actions:              []string{"deploy", "write"},
			Approvals:            []string{"security"},
			PolicyDecisions:      []string{"allow"},
			ProofRefs:            []string{"proof-1"},
			RuntimeProvider:      "openai",
			RuntimeHost:          "codex.internal.acme",
			RuntimeKind:          "hosted_agent",
			ModelProvider:        "openai",
			ModelVersion:         "gpt-5-codex",
			ExecutionEnvironment: "managed_platform",
			StateRetentionStatus: "retained",
			RetainedStateTypes: []string{
				"checkpoint_digest",
				"tool_result_digest",
			},
			StateLocationRefs: []string{"state://codex/sess-1"},
			StateDigestRefs:   []string{"sha256:0123456789abcdef"},
			CompletedAt:       "2026-05-27T14:00:00Z",
		}},
	}); err != nil {
		t.Fatalf("save runtime sessions: %v", err)
	}

	runtimeSessions := buildSessionSummary(statePath, snapshot)
	if runtimeSessions == nil || runtimeSessions.MatchedSessions != 1 {
		t.Fatalf("expected matched runtime session summary, got %+v", runtimeSessions)
	}
	runtimeEvidence := buildRuntimeEvidenceSummary(statePath, snapshot)
	if runtimeEvidence == nil || runtimeEvidence.MatchedRecords == 0 {
		t.Fatalf("expected projected runtime evidence summary, got %+v", runtimeEvidence)
	}
	evidencePackets := buildEvidencePacketSummary(statePath, snapshot)
	if evidencePackets == nil || evidencePackets.MatchedPackets != 1 {
		t.Fatalf("expected projected evidence packets summary, got %+v", evidencePackets)
	}
	enterprisePaths := decorateActionPathsForEnterpriseContext(
		append([]risk.ActionPath(nil), snapshot.RiskReport.ActionPaths...),
		runtimeSessions,
		evidencePackets,
	)
	if enterprisePaths[0].AgentIdentity == nil || enterprisePaths[0].AgentIdentity.RuntimeProvider != "openai" {
		t.Fatalf("expected runtime provider to flow into agent identity, got %+v", enterprisePaths[0].AgentIdentity)
	}
	if enterprisePaths[0].StateRetentionEvidenceState != risk.EvidenceStateVerified {
		t.Fatalf("expected retained state posture to stay verified when evidence exists, got %+v", enterprisePaths[0])
	}
	decoratedPaths := decorateBoundaryLabelsForReport(
		enterprisePaths,
		runtimeEvidenceByPath(runtimeEvidence),
		runtimeSessionsByPath(runtimeSessions),
		evidencePacketsByPath(evidencePackets),
	)
	if decoratedPaths[0].BoundaryLabel != BoundaryLabelApprovalCapable {
		t.Fatalf("expected approval_capable boundary label, got %+v", decoratedPaths[0])
	}
	graph := decorateControlPathGraphBoundary(risk.BuildControlPathGraph(decoratedPaths), decoratedPaths)
	if graph == nil || len(graph.Nodes) == 0 || graph.Nodes[0].BoundaryLabel != BoundaryLabelApprovalCapable {
		t.Fatalf("expected graph boundary label projection, got %+v", graph)
	}

	summary := Summary{
		GeneratedAt:      "2026-05-27T14:00:00Z",
		RuntimeSessions:  decorateSessionSummaryBoundary(runtimeSessions, decoratedPaths),
		RuntimeEvidence:  decorateRuntimeEvidenceSummaryBoundary(runtimeEvidence, decoratedPaths),
		EvidencePackets:  decorateEvidencePacketSummaryBoundary(evidencePackets, decoratedPaths),
		ActionPaths:      decoratedPaths,
		ControlPathGraph: graph,
	}
	bom := BuildAgentActionBOM(summary)
	if bom == nil || len(bom.Items) != 1 {
		t.Fatalf("expected one BOM item, got %+v", bom)
	}
	if bom.Items[0].RuntimeSessionStatus != ingest.CorrelationStatusMatched {
		t.Fatalf("expected matched runtime session status on BOM item, got %+v", bom.Items[0])
	}
	if len(bom.Items[0].ObservedSessionActions) == 0 || len(bom.Items[0].ObservedChangedFiles) == 0 {
		t.Fatalf("expected observed session actions and changed files on BOM item, got %+v", bom.Items[0])
	}
	if bom.Items[0].EvidencePacketStatus != ingest.CorrelationStatusMatched {
		t.Fatalf("expected matched evidence packet status on BOM item, got %+v", bom.Items[0])
	}
	if bom.Items[0].BoundaryLabel != BoundaryLabelApprovalCapable {
		t.Fatalf("expected approval_capable BOM boundary label, got %+v", bom.Items[0])
	}
	if bom.Items[0].RuntimeProvider != "openai" || bom.Items[0].ModelVersion != "gpt-5-codex" {
		t.Fatalf("expected runtime neutrality projection on BOM item, got %+v", bom.Items[0])
	}
	if bom.Items[0].StateRetentionStatus != "retained" || len(bom.Items[0].StateDigestRefs) != 1 {
		t.Fatalf("expected retention posture projection on BOM item, got %+v", bom.Items[0])
	}

	redacted := sanitizeAgentActionBOMWithConfig(bom, ShareProfileDesignPartner, ResolveRedactionConfig(ShareProfileDesignPartner, nil))
	if redacted.Items[0].RuntimeHost == "codex.internal.acme" || redacted.Items[0].ModelVersion == "gpt-5-codex" {
		t.Fatalf("expected redaction to hide host/model details, got %+v", redacted.Items[0])
	}
}

func TestEnterpriseContextLeavesMissingRetentionUnknown(t *testing.T) {
	t.Parallel()

	paths := decorateActionPathsForEnterpriseContext([]risk.ActionPath{{
		PathID:  "apc-1",
		AgentID: "wrkr:codex:test",
		Repo:    "acme/payments",
		Org:     "acme",
	}}, nil, &ingest.EvidencePacketSummary{
		Correlations: []ingest.EvidencePacketCorrelation{{
			PacketID: "packet-1",
			PathID:   "apc-1",
			Status:   ingest.CorrelationStatusMatched,
		}},
	})
	if len(paths) != 1 {
		t.Fatalf("expected one path, got %+v", paths)
	}
	if paths[0].StateRetentionStatus != "" {
		t.Fatalf("expected absent retention status to stay empty, got %+v", paths[0])
	}
	if paths[0].StateRetentionEvidenceState != risk.EvidenceStateUnknown {
		t.Fatalf("expected absent retention evidence to remain unknown, got %+v", paths[0])
	}
}
