package audit

import (
	"testing"
	"time"
)

// fixedNow returns a deterministic clock for tests that need a time source
// independent of row.EnqueuedAt. ExpandFieldRows never consumes this for
// ChangedAt (C6.1 invariant: derive from row.EnqueuedAt), but the helper is
// wired through the signature to allow future uses without a churn.
func fixedNow() time.Time {
	return time.Date(2026, 4, 24, 12, 0, 0, 0, time.UTC)
}

func testRow(enqueuedAt time.Time) OutboxRow {
	return OutboxRow{
		ID:         42,
		EnqueuedAt: enqueuedAt,
	}
}

func baseEvent(action AuditAction) AuditEvent {
	return AuditEvent{
		ObjectApiName: "Account",
		RecordId:      "001xx000001",
		Action:        action,
		ChangedById:   "005xx000001",
		Entrypoint:    EntrypointRestUI,
		Outcome:       OutcomeSuccess,
	}
}

func TestExpandFieldRows_CreateEmitsSingleSentinel(t *testing.T) {
	enq := time.Date(2026, 4, 23, 10, 0, 0, 0, time.UTC)
	row := testRow(enq)
	ev := baseEvent(ActionCreate)
	// Even if caller accidentally passed FieldDiffs on CREATE, CREATE
	// collapses them to a single __created__ sentinel.
	ev.FieldDiffs = []FieldDiff{{FieldName: "Name", OldValue: "", NewValue: "Acme"}}

	out := ExpandFieldRows(ev, row, fixedNow)

	if len(out) != 1 {
		t.Fatalf("CREATE expected 1 row, got %d", len(out))
	}
	if out[0].FieldName != FieldNameCreated {
		t.Errorf("expected FieldName=%q, got %q", FieldNameCreated, out[0].FieldName)
	}
	if !out[0].ChangedAt.Equal(enq.UTC()) {
		t.Errorf("ChangedAt must equal row.EnqueuedAt UTC, got %v", out[0].ChangedAt)
	}
	if out[0].OutboxRowID != row.ID {
		t.Errorf("OutboxRowID mismatch")
	}
}

func TestExpandFieldRows_Delete(t *testing.T) {
	row := testRow(time.Now().UTC())
	ev := baseEvent(ActionDelete)
	out := ExpandFieldRows(ev, row, fixedNow)
	if len(out) != 1 || out[0].FieldName != FieldNameDeleted {
		t.Fatalf("DELETE expected 1 __deleted__ row, got %+v", out)
	}
}

func TestExpandFieldRows_Undelete(t *testing.T) {
	row := testRow(time.Now().UTC())
	ev := baseEvent(ActionUndelete)
	out := ExpandFieldRows(ev, row, fixedNow)
	if len(out) != 1 || out[0].FieldName != FieldNameUndeleted {
		t.Fatalf("UNDELETE expected 1 __undeleted__ row, got %+v", out)
	}
}

func TestExpandFieldRows_Lock(t *testing.T) {
	row := testRow(time.Now().UTC())
	ev := baseEvent(ActionLock)
	out := ExpandFieldRows(ev, row, fixedNow)
	if len(out) != 1 || out[0].FieldName != FieldNameLocked {
		t.Fatalf("LOCK expected 1 __locked__ row, got %+v", out)
	}
}

func TestExpandFieldRows_Unlock(t *testing.T) {
	row := testRow(time.Now().UTC())
	ev := baseEvent(ActionUnlock)
	out := ExpandFieldRows(ev, row, fixedNow)
	if len(out) != 1 || out[0].FieldName != FieldNameUnlocked {
		t.Fatalf("UNLOCK expected 1 __unlocked__ row, got %+v", out)
	}
}

func TestExpandFieldRows_OwnerChange(t *testing.T) {
	row := testRow(time.Now().UTC())
	ev := baseEvent(ActionOwnerChange)
	ev.FieldDiffs = []FieldDiff{
		{FieldName: "OwnerId", OldValue: "005xx000001", NewValue: "005xx000002"},
	}
	out := ExpandFieldRows(ev, row, fixedNow)
	if len(out) != 1 {
		t.Fatalf("OWNER_CHANGE expected 1 row, got %d", len(out))
	}
	if out[0].FieldName != FieldNameOwner {
		t.Errorf("expected sentinel FieldName=%q, got %q", FieldNameOwner, out[0].FieldName)
	}
	if out[0].OldValue != "005xx000001" || out[0].NewValue != "005xx000002" {
		t.Errorf("OwnerId Old/New not carried through to Owner sentinel: %+v", out[0])
	}
}

func TestExpandFieldRows_UpdateOneRowPerField(t *testing.T) {
	row := testRow(time.Now().UTC())
	ev := baseEvent(ActionUpdate)
	ev.FieldDiffs = []FieldDiff{
		{FieldName: "Name", OldValue: "Old", NewValue: "New"},
		{FieldName: "Phone", OldValue: "", NewValue: "555-1234"},
	}
	out := ExpandFieldRows(ev, row, fixedNow)
	if len(out) != 2 {
		t.Fatalf("expected 2 rows, got %d", len(out))
	}
	if out[0].FieldName != "Name" || out[1].FieldName != "Phone" {
		t.Errorf("unexpected field order/names: %+v", out)
	}
	for _, r := range out {
		if r.Action != ActionUpdate {
			t.Errorf("expected Action=UPDATE on expanded row, got %s", r.Action)
		}
	}
}

func TestExpandFieldRows_UpdateSkipsNoopDiff(t *testing.T) {
	row := testRow(time.Now().UTC())
	ev := baseEvent(ActionUpdate)
	ev.FieldDiffs = []FieldDiff{
		{FieldName: "Name", OldValue: "Same", NewValue: "Same"},
		{FieldName: "Phone", OldValue: "", NewValue: "555-1234"},
	}
	out := ExpandFieldRows(ev, row, fixedNow)
	if len(out) != 1 || out[0].FieldName != "Phone" {
		t.Fatalf("expected 1 row (Phone only), got %+v", out)
	}
}

func TestExpandFieldRows_UpdateSkipsPIIFields(t *testing.T) {
	row := testRow(time.Now().UTC())
	ev := baseEvent(ActionUpdate)
	ev.FieldDiffs = []FieldDiff{
		{FieldName: "Password", OldValue: "old", NewValue: "new"},
		{FieldName: "SessionKey", OldValue: "s1", NewValue: "s2"},
		{FieldName: "ApiToken", OldValue: "t1", NewValue: "t2"},
		{FieldName: "MfaSecret", OldValue: "m1", NewValue: "m2"},
		{FieldName: "Name", OldValue: "Old", NewValue: "New"},
	}
	out := ExpandFieldRows(ev, row, fixedNow)
	if len(out) != 1 || out[0].FieldName != "Name" {
		t.Fatalf("expected only Name (PII fields skipped), got %+v", out)
	}
}

func TestExpandFieldRows_UpdateSkipsLongTextBothNull(t *testing.T) {
	row := testRow(time.Now().UTC())
	ev := baseEvent(ActionUpdate)
	ev.FieldDiffs = []FieldDiff{
		// Both empty — treated as LongText-both-null noise.
		{FieldName: "Description", OldValue: "", NewValue: ""},
		{FieldName: "Name", OldValue: "Old", NewValue: "New"},
	}
	out := ExpandFieldRows(ev, row, fixedNow)
	if len(out) != 1 || out[0].FieldName != "Name" {
		t.Fatalf("expected only Name (both-null Description skipped), got %+v", out)
	}
}

func TestExpandFieldRows_UpdateEmptyDiffs(t *testing.T) {
	row := testRow(time.Now().UTC())
	ev := baseEvent(ActionUpdate)
	ev.FieldDiffs = nil
	out := ExpandFieldRows(ev, row, fixedNow)
	if len(out) != 0 {
		t.Fatalf("expected 0 rows on empty diffs, got %d", len(out))
	}
}

func TestExpandFieldRows_OwnerIdUnderUpdateEmitsRegularRow(t *testing.T) {
	// Defensive: if a caller slips OwnerId diff under Action=UPDATE
	// (emitter should have normalized per §3.6 F2, but audit-expand layer
	// is downstream and must not crash). Policy: log-and-emit a regular
	// OwnerId row — do not upgrade the sentinel here; correctness of the
	// sentinel contract is the emitter's responsibility.
	row := testRow(time.Now().UTC())
	ev := baseEvent(ActionUpdate)
	ev.FieldDiffs = []FieldDiff{
		{FieldName: "OwnerId", OldValue: "005a", NewValue: "005b"},
		{FieldName: "Name", OldValue: "Old", NewValue: "New"},
	}
	out := ExpandFieldRows(ev, row, fixedNow)
	if len(out) != 2 {
		t.Fatalf("expected 2 rows (OwnerId + Name), got %d: %+v", len(out), out)
	}
	if out[0].FieldName != "OwnerId" {
		t.Errorf("expected OwnerId regular row, got %q", out[0].FieldName)
	}
}

func TestExpandFieldRows_ChangedAtDerivedFromEnqueuedAt(t *testing.T) {
	// C6.1 invariant: ChangedAt MUST equal row.EnqueuedAt.UTC(), not now().
	enq := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
	row := testRow(enq)
	ev := baseEvent(ActionUpdate)
	ev.FieldDiffs = []FieldDiff{{FieldName: "Name", OldValue: "A", NewValue: "B"}}
	out := ExpandFieldRows(ev, row, func() time.Time {
		return time.Date(2099, 12, 31, 0, 0, 0, 0, time.UTC)
	})
	if !out[0].ChangedAt.Equal(enq.UTC()) {
		t.Fatalf("ChangedAt must be row.EnqueuedAt UTC, got %v", out[0].ChangedAt)
	}
}
