package audit

import (
	"strings"
	"testing"
)

// TestAuditEntrypoint_AllValuesValidate asserts every valid Entrypoint value
// passes Validate() when paired with otherwise-complete required fields.
// Ensures enum exhaustiveness per design §3.5 and catches accidental removal.
func TestAuditEntrypoint_AllValuesValidate(t *testing.T) {
	t.Parallel()

	cases := []struct {
		name string
		ep   AuditEntrypoint
	}{
		{"REST_API", EntrypointRestAPI},
		{"REST_UI", EntrypointRestUI},
		{"FLOW", EntrypointFlow},
		{"TRIGGER", EntrypointTrigger},
		{"BULK_API", EntrypointBulkAPI},
		{"CONVERT", EntrypointConvert},
		{"APPROVAL", EntrypointApproval},
		{"SCHEDULER", EntrypointScheduler},
		{"SETUP", EntrypointSetup},
		{"AUTH", EntrypointAuth},
		{"UNKNOWN", EntrypointUnknown},
	}

	for _, tc := range cases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			ev := newValidBaseEvent()
			ev.Entrypoint = tc.ep
			if err := ev.Validate(); err != nil {
				t.Fatalf("entrypoint %q rejected: %v", tc.ep, err)
			}
			if string(tc.ep) != tc.name {
				t.Errorf("entrypoint string = %q, want %q", tc.ep, tc.name)
			}
		})
	}
}

// TestAuditEvent_ZeroEntrypointRejected locks the CH-4 mitigation: an
// unset Entrypoint MUST fail loud via Validate(); no silent fallback.
func TestAuditEvent_ZeroEntrypointRejected(t *testing.T) {
	t.Parallel()

	ev := newValidBaseEvent()
	ev.Entrypoint = AuditEntrypoint("") // zero value

	err := ev.Validate()
	if err == nil {
		t.Fatal("expected zero Entrypoint to be rejected; got nil")
	}
	if !strings.Contains(err.Error(), "Entrypoint") {
		t.Errorf("error message should mention Entrypoint, got: %v", err)
	}
}

// TestAuditEvent_RequiredFieldsRejected covers the rest of the REQUIRED
// contract — ObjectApiName, RecordId, Action, ChangedById, Outcome.
func TestAuditEvent_RequiredFieldsRejected(t *testing.T) {
	t.Parallel()

	cases := []struct {
		name    string
		mutate  func(*AuditEvent)
		wantSub string
	}{
		{"ObjectApiName", func(e *AuditEvent) { e.ObjectApiName = "" }, "ObjectApiName"},
		{"RecordId", func(e *AuditEvent) { e.RecordId = "" }, "RecordId"},
		{"Action", func(e *AuditEvent) { e.Action = AuditAction("") }, "Action"},
		{"ChangedById", func(e *AuditEvent) { e.ChangedById = "" }, "ChangedById"},
		{"Outcome", func(e *AuditEvent) { e.Outcome = Outcome("") }, "Outcome"},
	}

	for _, tc := range cases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			ev := newValidBaseEvent()
			tc.mutate(&ev)
			err := ev.Validate()
			if err == nil {
				t.Fatalf("expected missing %s to be rejected", tc.name)
			}
			if !strings.Contains(err.Error(), tc.wantSub) {
				t.Errorf("error should mention %q, got: %v", tc.wantSub, err)
			}
		})
	}
}

// TestAuditAction_AllValuesRoundTrip asserts every declared AuditAction is
// a non-empty string constant and equals its own string representation.
// Catches typos / duplicate literals in enum definitions.
func TestAuditAction_AllValuesRoundTrip(t *testing.T) {
	t.Parallel()

	cases := []struct {
		name string
		a    AuditAction
	}{
		{"CREATE", ActionCreate},
		{"UPDATE", ActionUpdate},
		{"DELETE", ActionDelete},
		{"UNDELETE", ActionUndelete},
		{"OWNER_CHANGE", ActionOwnerChange},
		{"LOCK", ActionLock},
		{"UNLOCK", ActionUnlock},
		{"LOGIN_AS", ActionLoginAs},
		{"CONVERT", ActionConvert},
		{"APPROVE", ActionApprove},
		{"REJECT", ActionReject},
		{"SUBMIT", ActionSubmit},
		{"EXPORT_AUDIT", ActionExportAudit},
	}

	seen := map[string]bool{}
	for _, tc := range cases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			if string(tc.a) != tc.name {
				t.Errorf("action %q string = %q, want %q", tc.name, string(tc.a), tc.name)
			}
			if string(tc.a) == "" {
				t.Errorf("action %q is empty", tc.name)
			}
			if seen[string(tc.a)] {
				t.Errorf("duplicate action literal: %q", tc.a)
			}
			seen[string(tc.a)] = true
		})
	}
}

// TestOutcome_Values asserts Outcome enum has exactly SUCCESS and FAILURE.
func TestOutcome_Values(t *testing.T) {
	t.Parallel()
	if string(OutcomeSuccess) != "SUCCESS" {
		t.Errorf("OutcomeSuccess = %q, want SUCCESS", OutcomeSuccess)
	}
	if string(OutcomeFailure) != "FAILURE" {
		t.Errorf("OutcomeFailure = %q, want FAILURE", OutcomeFailure)
	}
}

// newValidBaseEvent returns an AuditEvent with all REQUIRED fields populated
// so tests can mutate one field at a time.
func newValidBaseEvent() AuditEvent {
	return AuditEvent{
		ObjectApiName: "Account",
		RecordId:      "001xx0000000001",
		Action:        ActionUpdate,
		ChangedById:   "005xx0000000001",
		Entrypoint:    EntrypointRestAPI,
		Outcome:       OutcomeSuccess,
	}
}
