package replay

import (
	"testing"
)

func TestDiffAuditTrails_EqualTrails(t *testing.T) {
	rows := []AuditRow{
		{ObjectApiName: "Lead", RecordId: "001", FieldName: "Status", Action: "UPDATE", OldValue: "Open", NewValue: "Converted"},
		{ObjectApiName: "Lead", RecordId: "001", FieldName: "__record__", Action: "UPDATE"},
	}
	got := DiffAuditTrails(rows, rows, DiffModeTolerant)
	if !got.Match {
		t.Fatalf("expected Match=true, got=%+v", got)
	}
	if len(got.Differences) != 0 {
		t.Errorf("expected zero diffs, got %d", len(got.Differences))
	}
}

func TestDiffAuditTrails_Reordered(t *testing.T) {
	a := []AuditRow{
		{ObjectApiName: "Lead", RecordId: "001", FieldName: "Status", Action: "UPDATE", NewValue: "Converted"},
		{ObjectApiName: "Lead", RecordId: "001", FieldName: "Country", Action: "UPDATE", NewValue: "US"},
	}
	b := []AuditRow{
		{ObjectApiName: "Lead", RecordId: "001", FieldName: "Country", Action: "UPDATE", NewValue: "US"},
		{ObjectApiName: "Lead", RecordId: "001", FieldName: "Status", Action: "UPDATE", NewValue: "Converted"},
	}
	got := DiffAuditTrails(a, b, DiffModeTolerant)
	if !got.Match {
		t.Errorf("reorder alone must not diverge; got %+v", got)
	}
}

func TestDiffAuditTrails_MissingRow(t *testing.T) {
	expected := []AuditRow{
		{ObjectApiName: "Lead", RecordId: "001", FieldName: "Status", Action: "UPDATE", NewValue: "Converted"},
		{ObjectApiName: "Lead", RecordId: "001", FieldName: "Country", Action: "UPDATE", NewValue: "US"},
	}
	replay := []AuditRow{
		{ObjectApiName: "Lead", RecordId: "001", FieldName: "Status", Action: "UPDATE", NewValue: "Converted"},
	}
	got := DiffAuditTrails(expected, replay, DiffModeTolerant)
	if got.Match {
		t.Fatal("expected Match=false")
	}
	if len(got.Differences) != 1 || got.Differences[0].Kind != "missing" {
		t.Errorf("expected one 'missing' diff, got %+v", got.Differences)
	}
}

func TestDiffAuditTrails_AddedRow(t *testing.T) {
	expected := []AuditRow{
		{ObjectApiName: "Lead", RecordId: "001", FieldName: "Status", Action: "UPDATE", NewValue: "Converted"},
	}
	replay := []AuditRow{
		{ObjectApiName: "Lead", RecordId: "001", FieldName: "Status", Action: "UPDATE", NewValue: "Converted"},
		{ObjectApiName: "Lead", RecordId: "001", FieldName: "UnexpectedField", Action: "UPDATE", NewValue: "x"},
	}
	got := DiffAuditTrails(expected, replay, DiffModeTolerant)
	if got.Match {
		t.Fatal("expected Match=false")
	}
	if len(got.Differences) != 1 || got.Differences[0].Kind != "added" {
		t.Errorf("expected one 'added' diff, got %+v", got.Differences)
	}
}

func TestDiffAuditTrails_FieldValueChanged(t *testing.T) {
	expected := []AuditRow{
		{ObjectApiName: "Lead", RecordId: "001", FieldName: "Status", Action: "UPDATE", OldValue: "Open", NewValue: "Converted"},
	}
	replay := []AuditRow{
		{ObjectApiName: "Lead", RecordId: "001", FieldName: "Status", Action: "UPDATE", OldValue: "Open", NewValue: "Qualified"},
	}
	got := DiffAuditTrails(expected, replay, DiffModeTolerant)
	if got.Match {
		t.Fatal("expected Match=false")
	}
	if len(got.Differences) != 1 || got.Differences[0].Kind != "field-changed" {
		t.Errorf("expected 'field-changed' diff, got %+v", got.Differences)
	}
}

// TestDiffAuditTrails_Multiset_DuplicateRowsNotCollapsed pins the fix for
// V1-HIGH: a legitimate request can emit multiple audit rows sharing the
// same (ObjectApiName, RecordId, FieldName, Action) tuple (e.g. two Owner
// changes in a batch). The old differ keyed by tuple and kept only the
// first occurrence, silently dropping duplicates and reporting a false
// match. The new differ is count-aware.
func TestDiffAuditTrails_Multiset_DuplicateRowsNotCollapsed(t *testing.T) {
	expected := []AuditRow{
		{ObjectApiName: "Lead", RecordId: "001", FieldName: "OwnerId", Action: "UPDATE", OldValue: "u1", NewValue: "u2"},
		{ObjectApiName: "Lead", RecordId: "001", FieldName: "OwnerId", Action: "UPDATE", OldValue: "u2", NewValue: "u3"},
	}
	replay := []AuditRow{
		{ObjectApiName: "Lead", RecordId: "001", FieldName: "OwnerId", Action: "UPDATE", OldValue: "u1", NewValue: "u2"},
	}
	got := DiffAuditTrails(expected, replay, DiffModeTolerant)
	if got.Match {
		t.Fatal("expected Match=false: 2 expected rows vs 1 replay row for same key")
	}
	if len(got.Differences) != 1 || got.Differences[0].Kind != "count-mismatch" {
		t.Errorf("expected one 'count-mismatch' diff, got %+v", got.Differences)
	}
}

// TestDiffAuditTrails_Multiset_EqualDuplicatesMatch: two identical
// duplicate rows on each side → match (reorder-within-bucket also ok).
func TestDiffAuditTrails_Multiset_EqualDuplicatesMatch(t *testing.T) {
	expected := []AuditRow{
		{ObjectApiName: "Lead", RecordId: "001", FieldName: "OwnerId", Action: "UPDATE", OldValue: "u1", NewValue: "u2"},
		{ObjectApiName: "Lead", RecordId: "001", FieldName: "OwnerId", Action: "UPDATE", OldValue: "u2", NewValue: "u3"},
	}
	replay := []AuditRow{
		// Reversed order within the same bucket.
		{ObjectApiName: "Lead", RecordId: "001", FieldName: "OwnerId", Action: "UPDATE", OldValue: "u2", NewValue: "u3"},
		{ObjectApiName: "Lead", RecordId: "001", FieldName: "OwnerId", Action: "UPDATE", OldValue: "u1", NewValue: "u2"},
	}
	got := DiffAuditTrails(expected, replay, DiffModeTolerant)
	if !got.Match {
		t.Errorf("multiset equality with within-bucket reorder should match; got %+v", got)
	}
}

func TestDiffAuditTrails_TolerantIgnoresEntrypoint(t *testing.T) {
	expected := []AuditRow{
		{ObjectApiName: "Lead", RecordId: "001", FieldName: "Status", Action: "UPDATE", NewValue: "Converted", Entrypoint: "FLOW"},
	}
	replay := []AuditRow{
		{ObjectApiName: "Lead", RecordId: "001", FieldName: "Status", Action: "UPDATE", NewValue: "Converted", Entrypoint: "TRIGGER"},
	}
	gotT := DiffAuditTrails(expected, replay, DiffModeTolerant)
	if !gotT.Match {
		t.Errorf("tolerant mode must ignore entrypoint; got %+v", gotT)
	}
	gotS := DiffAuditTrails(expected, replay, DiffModeStrict)
	if gotS.Match {
		t.Errorf("strict mode must flag entrypoint diff; got %+v", gotS)
	}
}
