package audit

import (
	"testing"
	"time"
)

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

	// Empty type should return an error
	e := Event{Source: SourceEdge, Timestamp: time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)}
	err := e.Validate()
	if err == nil {
		t.Fatal("expected error for missing event type")
	}
	if got := err.Error(); got != "audit event type is required" {
		t.Fatalf("expected 'audit event type is required', got %q", got)
	}

	// Empty source should return an error
	e = Event{Type: TypeExecutionRequested, Timestamp: time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)}
	err = e.Validate()
	if err == nil {
		t.Fatal("expected error for missing source")
	}
	if got := err.Error(); got != "audit event source is required" {
		t.Fatalf("expected 'audit event source is required', got %q", got)
	}

	// Zero timestamp should return an error
	e = Event{Type: TypeExecutionRequested, Source: SourceNode}
	err = e.Validate()
	if err == nil {
		t.Fatal("expected error for zero timestamp")
	}
	if got := err.Error(); got != "audit event timestamp is required" {
		t.Fatalf("expected 'audit event timestamp is required', got %q", got)
	}

	// All required fields present should pass
	e = Event{
		Type:      TypeExecutionCompleted,
		Source:    SourceControlPlane,
		Timestamp: time.Date(2026, 6, 1, 12, 0, 0, 0, time.UTC),
	}
	if err := e.Validate(); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
}

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

	ts := time.Date(2026, 6, 1, 15, 30, 0, 0, time.UTC)
	e := Event{
		EventID:        "evt-001",
		Type:           TypeExecutionPolicyEvaluated,
		Source:         SourceEdge,
		ActorID:        "user-42",
		RequestID:      "req-100",
		RunID:          "run-200",
		SessionID:      "sess-300",
		EdgeID:         "edge-1",
		NodeID:         "node-5",
		Adapter:        "k8s",
		Target:         "deploy/web",
		CredentialRef:  MetadataCredentialReference,
		PolicyDecision: DecisionAllow,
		PolicyRule:     "rule-default-allow",
		Timestamp:      ts,
		Status:         "passed",
		Metadata: map[string]string{
			"extra": "value",
		},
	}
	if err := e.Validate(); err != nil {
		t.Fatalf("unexpected validation error: %v", err)
	}
}

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

	ts := time.Date(2026, 6, 2, 10, 0, 0, 0, time.UTC)
	e := Event{
		Type:      TypeTerminalSessionClosed,
		Source:    SourceNode,
		EventID:   "evt-term-01",
		NodeID:    "node-abc",
		Timestamp: ts,
	}
	if err := e.Validate(); err != nil {
		t.Fatalf("unexpected validation error: %v", err)
	}
}

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

	ts := time.Date(2026, 6, 3, 8, 0, 0, 0, time.UTC)
	e := Event{
		Type:      TypeBootstrapEnrollmentRequested,
		Source:    SourceControlPlane,
		EventID:   "evt-boot-01",
		Timestamp: ts,
	}
	if err := e.Validate(); err != nil {
		t.Fatalf("unexpected validation error: %v", err)
	}
}

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

	// nil metadata should return nil
	e := Event{
		Type:      TypeExecutionRequested,
		Source:    SourceEdge,
		Timestamp: time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC),
	}
	if got := e.CopyMetadata(); got != nil {
		t.Fatalf("expected nil for nil metadata, got %v", got)
	}

	// Non-nil metadata should be defensively copied
	original := map[string]string{
		"foo": "bar",
		"key": "val",
	}
	e.Metadata = original
	copied := e.CopyMetadata()

	if len(copied) != len(original) {
		t.Fatalf("expected copied map length %d, got %d", len(original), len(copied))
	}
	if copied["foo"] != "bar" {
		t.Fatalf("expected copied['foo'] == 'bar', got %q", copied["foo"])
	}
	if copied["key"] != "val" {
		t.Fatalf("expected copied['key'] == 'val', got %q", copied["key"])
	}

	// Modifying the copy must not affect the original
	copied["foo"] = "changed"
	if original["foo"] != "bar" {
		t.Fatal("original metadata was mutated by changing the copy")
	}

	// Modifying the original must not affect the copy
	original["key"] = "changed"
	if copied["key"] != "val" {
		t.Fatal("copied metadata was mutated by changing the original")
	}
}

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

	// MetadataCredentialReference should not contain raw secret text
	if MetadataCredentialReference == "" {
		t.Fatal("MetadataCredentialReference should not be empty")
	}
	if MetadataRedactionMarker == "" {
		t.Fatal("MetadataRedactionMarker should not be empty")
	}

	// Neither should contain raw terminal transcript or secret content
	if MetadataCredentialReference != "credential_ref" {
		t.Fatalf("expected 'credential_ref', got %q", MetadataCredentialReference)
	}
	if MetadataRedactionMarker != "redaction_marker" {
		t.Fatalf("expected 'redaction_marker', got %q", MetadataRedactionMarker)
	}
}

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

	expected := []struct {
		name string
		got  EventType
		want string
	}{
		{"TypeExecutionRequested", TypeExecutionRequested, "execution.requested"},
		{"TypeExecutionPolicyEvaluated", TypeExecutionPolicyEvaluated, "execution.policy_evaluated"},
		{"TypeExecutionDispatched", TypeExecutionDispatched, "execution.dispatched"},
		{"TypeExecutionStarted", TypeExecutionStarted, "execution.started"},
		{"TypeExecutionCompleted", TypeExecutionCompleted, "execution.completed"},
		{"TypeExecutionFailed", TypeExecutionFailed, "execution.failed"},
		{"TypeExecutionCanceled", TypeExecutionCanceled, "execution.canceled"},

		{"TypeTerminalSessionRequested", TypeTerminalSessionRequested, "terminal.session_requested"},
		{"TypeTerminalSessionStarted", TypeTerminalSessionStarted, "terminal.session_started"},
		{"TypeTerminalCommandSubmitted", TypeTerminalCommandSubmitted, "terminal.command_submitted"},
		{"TypeTerminalCommandCompleted", TypeTerminalCommandCompleted, "terminal.command_completed"},
		{"TypeTerminalSessionClosed", TypeTerminalSessionClosed, "terminal.session_closed"},

		{"TypeBootstrapEnrollmentRequested", TypeBootstrapEnrollmentRequested, "bootstrap.enrollment_requested"},
		{"TypeBootstrapCredentialIssued", TypeBootstrapCredentialIssued, "bootstrap.credential_issued"},
		{"TypeBootstrapNodeRegistered", TypeBootstrapNodeRegistered, "bootstrap.node_registered"},
		{"TypeBootstrapEnrollmentFailed", TypeBootstrapEnrollmentFailed, "bootstrap.enrollment_failed"},
		{"TypeBootstrapRevoked", TypeBootstrapRevoked, "bootstrap.revoked"},
	}

	if len(expected) != 17 {
		t.Fatalf("expected 17 event type constants, got %d", len(expected))
	}

	seen := map[string]string{}
	for _, tt := range expected {
		if string(tt.got) != tt.want {
			t.Fatalf("%s = %q, want %q", tt.name, tt.got, tt.want)
		}
		if previous, ok := seen[tt.want]; ok {
			t.Fatalf("duplicate event type value %q for %s and %s", tt.want, previous, tt.name)
		}
		seen[tt.want] = tt.name
	}
}

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

	expectedSources := []Source{SourceControlPlane, SourceEdge, SourceNode}
	for _, s := range expectedSources {
		if string(s) == "" {
			t.Fatalf("source constant resolved to empty string: %v", s)
		}
	}
}

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

	expectedDecisions := []PolicyDecision{DecisionAllow, DecisionDeny, DecisionWarn}
	for _, d := range expectedDecisions {
		if string(d) == "" {
			t.Fatalf("policy decision constant resolved to empty string: %v", d)
		}
	}
}

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

	if DefaultEdgeLocalRetentionDays != 30 {
		t.Fatalf("expected DefaultEdgeLocalRetentionDays == 30, got %d", DefaultEdgeLocalRetentionDays)
	}
	if DefaultControlPlaneMetadataRetentionDays != 90 {
		t.Fatalf("expected DefaultControlPlaneMetadataRetentionDays == 90, got %d", DefaultControlPlaneMetadataRetentionDays)
	}
}
