package audit

import (
	"errors"
	"testing"
)

// TestBuildBulkEvents_HappyPath asserts that a mixed batch produces one event
// per mutation, each with Entrypoint=BULK_API and the correct Action derived
// from the per-record op. The batch-level JobId lands in EntrypointDetail and
// the shared RequestId/TraceId propagate to every event.
func TestBuildBulkEvents_HappyPath(t *testing.T) {
	t.Parallel()

	auth := BulkAuthContext{
		ChangedById: "005xx0000000001",
		RequestId:   "req-bulk-1",
		TraceId:     "trace-bulk-1",
	}
	batch := BulkBatch{
		JobId: "750xx0000000ABC",
		Records: []BulkRecord{
			{ObjectApiName: "Account", RecordId: "001A", Op: BulkOpCreate},
			{ObjectApiName: "Account", RecordId: "001B", Op: BulkOpUpdate, FieldDiffs: []FieldDiff{{FieldName: "Name", OldValue: "x", NewValue: "y"}}},
			{ObjectApiName: "Lead", RecordId: "00QA", Op: BulkOpDelete},
		},
	}

	events, err := BuildBulkEvents(auth, batch)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if got, want := len(events), 3; got != want {
		t.Fatalf("len(events) = %d, want %d", got, want)
	}

	wantActions := []AuditAction{ActionCreate, ActionUpdate, ActionDelete}
	for i, ev := range events {
		if ev.Entrypoint != EntrypointBulkAPI {
			t.Errorf("event[%d].Entrypoint = %q, want BULK_API", i, ev.Entrypoint)
		}
		if ev.EntrypointDetail != batch.JobId {
			t.Errorf("event[%d].EntrypointDetail = %q, want %q", i, ev.EntrypointDetail, batch.JobId)
		}
		if ev.Action != wantActions[i] {
			t.Errorf("event[%d].Action = %q, want %q", i, ev.Action, wantActions[i])
		}
		if ev.RequestId != auth.RequestId || ev.TraceId != auth.TraceId {
			t.Errorf("event[%d] request/trace propagation failed", i)
		}
		if ev.ChangedById != auth.ChangedById {
			t.Errorf("event[%d].ChangedById = %q, want %q", i, ev.ChangedById, auth.ChangedById)
		}
		if ev.Outcome != OutcomeSuccess {
			t.Errorf("event[%d].Outcome = %q, want SUCCESS", i, ev.Outcome)
		}
		if err := ev.Validate(); err != nil {
			t.Errorf("event[%d] Validate() failed: %v", i, err)
		}
	}

	if got := events[1].FieldDiffs; len(got) != 1 || got[0].FieldName != "Name" {
		t.Errorf("event[1].FieldDiffs not propagated: %+v", got)
	}
}

// TestBuildBulkEvents_EmptyBatch returns no events and no error.
func TestBuildBulkEvents_EmptyBatch(t *testing.T) {
	t.Parallel()
	events, err := BuildBulkEvents(BulkAuthContext{ChangedById: "005"}, BulkBatch{JobId: "750"})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(events) != 0 {
		t.Fatalf("expected empty events, got %d", len(events))
	}
}

// TestBuildBulkEvents_InvalidInput rejects unknown op, missing ChangedById, missing RecordId.
func TestBuildBulkEvents_InvalidInput(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name string
		auth BulkAuthContext
		b    BulkBatch
	}{
		{
			name: "missing ChangedById",
			auth: BulkAuthContext{},
			b:    BulkBatch{JobId: "j", Records: []BulkRecord{{ObjectApiName: "Account", RecordId: "001", Op: BulkOpCreate}}},
		},
		{
			name: "unknown op",
			auth: BulkAuthContext{ChangedById: "005"},
			b:    BulkBatch{JobId: "j", Records: []BulkRecord{{ObjectApiName: "Account", RecordId: "001", Op: BulkOp("WEIRD")}}},
		},
		{
			name: "missing RecordId",
			auth: BulkAuthContext{ChangedById: "005"},
			b:    BulkBatch{JobId: "j", Records: []BulkRecord{{ObjectApiName: "Account", Op: BulkOpCreate}}},
		},
	}
	for _, tc := range tests {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			_, err := BuildBulkEvents(tc.auth, tc.b)
			if err == nil {
				t.Fatal("expected error, got nil")
			}
			// Missing RecordId is caught by Validate (wraps ErrMissingRequired).
			// The other two are caught pre-validate with a distinct error.
			if tc.name == "missing RecordId" && !errors.Is(err, ErrMissingRequired) {
				t.Errorf("expected ErrMissingRequired, got %v", err)
			}
		})
	}
}
