package ucca

import (
	"os"
	"path/filepath"
	"testing"
)

func TestControlMapping_Validate(t *testing.T) {
	candidate := ControlMapping{SourceNorm: "CRA Annex I", TargetFramework: "OWASP ASVS", TargetControl: "V6.3.1", MappingType: "supports", MappingStatus: "candidate", Provenance: "retriever_candidate"}
	if err := candidate.Validate(); err != nil {
		t.Fatalf("valid candidate rejected: %v", err)
	}
	accepted := ControlMapping{SourceNorm: "A", TargetFramework: "X", TargetControl: "Y", MappingType: "implements", MappingStatus: "accepted", Provenance: "human_curated", ReviewedBy: "benjamin", ReviewDate: "2026-06-25", ReviewReason: "passt"}
	if err := accepted.Validate(); err != nil {
		t.Fatalf("valid accepted rejected: %v", err)
	}

	bad := []struct {
		name string
		m    ControlMapping
	}{
		{"no source_norm", ControlMapping{TargetFramework: "X", TargetControl: "Y", MappingType: "supports", MappingStatus: "candidate", Provenance: "retriever_candidate"}},
		{"bad mapping_type", ControlMapping{SourceNorm: "A", TargetFramework: "X", TargetControl: "Y", MappingType: "nope", MappingStatus: "candidate", Provenance: "retriever_candidate"}},
		{"bad mapping_status", ControlMapping{SourceNorm: "A", TargetFramework: "X", TargetControl: "Y", MappingType: "supports", MappingStatus: "maybe", Provenance: "retriever_candidate"}},
		{"bad provenance", ControlMapping{SourceNorm: "A", TargetFramework: "X", TargetControl: "Y", MappingType: "supports", MappingStatus: "candidate", Provenance: "guessed"}},
		{"accepted without audit trail", ControlMapping{SourceNorm: "A", TargetFramework: "X", TargetControl: "Y", MappingType: "supports", MappingStatus: "accepted", Provenance: "human_curated"}},
		{"rejected without reason", ControlMapping{SourceNorm: "A", TargetFramework: "X", TargetControl: "Y", MappingType: "supports", MappingStatus: "rejected", Provenance: "human_curated", ReviewedBy: "b", ReviewDate: "2026-06-25"}},
	}
	for _, tt := range bad {
		if err := tt.m.Validate(); err == nil {
			t.Errorf("%s: expected rejection", tt.name)
		}
	}
}

func TestLoadControlMappings(t *testing.T) {
	dir := t.TempDir()
	content := `// header comment, ignored
{"source_norm":"CRA Annex I","source_role":"operational_requirement","target_framework":"OWASP ASVS","target_control":"V6.3.1","mapping_type":"supports","mapping_status":"accepted","provenance":"human_curated","reviewed_by":"benjamin","review_date":"2026-06-25","review_reason":"V6=Auth passt","rationale":"r","version":"2026-06-25"}
{"source_norm":"CRA Annex I","source_role":"operational_requirement","target_framework":"OWASP ASVS","target_control":"V14.2.4","mapping_type":"related","mapping_status":"candidate","provenance":"retriever_candidate","rationale":"r","version":"2026-06-25"}

`
	if err := os.WriteFile(filepath.Join(dir, "m.jsonl"), []byte(content), 0o644); err != nil {
		t.Fatal(err)
	}
	set, err := LoadControlMappings(dir)
	if err != nil {
		t.Fatalf("load: %v", err)
	}
	if len(set.All) != 2 {
		t.Fatalf("want 2 mappings, got %d", len(set.All))
	}
	if got := set.ControlsFor("CRA Annex I", false); len(got) != 2 {
		t.Errorf("ControlsFor(all): want 2, got %d", len(got))
	}
	if got := set.ControlsFor("CRA Annex I", true); len(got) != 1 {
		t.Errorf("ControlsFor(acceptedOnly): want 1 (only accepted), got %d", len(got))
	}
	if got := set.ObligationsFor("OWASP ASVS", "V6.3.1", true); len(got) != 1 {
		t.Errorf("ObligationsFor accepted reverse lookup: want 1, got %d", len(got))
	}
}

func TestLoadControlMappings_RejectsInvalid(t *testing.T) {
	dir := t.TempDir()
	// accepted without the who/when/why audit trail must fail-closed.
	if err := os.WriteFile(filepath.Join(dir, "bad.jsonl"), []byte(`{"source_norm":"A","target_framework":"X","target_control":"Y","mapping_type":"supports","mapping_status":"accepted","provenance":"human_curated","rationale":"r","version":"v"}`), 0o644); err != nil {
		t.Fatal(err)
	}
	if _, err := LoadControlMappings(dir); err == nil {
		t.Error("accepted mapping without audit trail must fail the load (fail-closed)")
	}
}

func TestControlMappings_SeedFileValid(t *testing.T) {
	// The committed seed store must always load + validate.
	set, err := LoadControlMappings("../../data/control_mappings")
	if err != nil {
		t.Fatalf("seed control_mappings failed to load: %v", err)
	}
	if len(set.All) == 0 {
		t.Fatal("seed control_mappings is empty")
	}
}
