package engagements

import (
	"context"
	"reflect"
	"strings"
	"testing"

	"gitea.maximumdirect.net/eric/notarius/internal/core/source"
	"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
	"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
	"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
)

func TestValidatorEnforcesOneEngagementPerSubject(t *testing.T) {
	for _, test := range []struct {
		name     string
		value    dnd.EnemyEventList
		approved bool
	}{
		{name: "empty", value: dnd.EnemyEventList{Events: []dnd.EnemyEvent{}}, approved: true},
		{name: "one engagement", value: events(event("Ashfang", dnd.EnemyEventKindEngaged, 1)), approved: true},
		{name: "same subject different evidence", value: events(event("Ashfang", dnd.EnemyEventKindEngaged, 1), event("Ashfang", dnd.EnemyEventKindEngaged, 2))},
		{name: "whitespace and case variant", value: events(event("Ashfang", dnd.EnemyEventKindEngaged, 1), event("  ASHFANG  ", dnd.EnemyEventKindEngaged, 2))},
		{name: "different subjects", value: events(event("Ashfang", dnd.EnemyEventKindEngaged, 1), event("Briar", dnd.EnemyEventKindEngaged, 2)), approved: true},
		{name: "different kinds", value: events(event("Ashfang", dnd.EnemyEventKindEngaged, 1), event("Ashfang", dnd.EnemyEventKindFled, 2)), approved: true},
		{name: "malformed shape", value: dnd.EnemyEventList{Events: []dnd.EnemyEvent{{Name: "Ashfang"}, {Name: "Ashfang", Kind: dnd.EnemyEventKindEngaged, SourceRefs: refs(2)}}}, approved: true},
	} {
		t.Run(test.name, func(t *testing.T) {
			result, err := New(Options{}).Validate(context.Background(), contracts.TypedValidationRequest[dnd.EnemyEventList]{Value: test.value})
			if err != nil || result.Approved != test.approved {
				t.Fatalf("Validate() = %#v, %v; want approved=%t", result, err, test.approved)
			}
			if !test.approved && result.ReasonCode != ReasonCode {
				t.Fatalf("reason code = %q, want %q", result.ReasonCode, ReasonCode)
			}
		})
	}
	validator := New(Options{})
	for _, unitID := range []int{1, 2} {
		value := events(event("Ashfang", dnd.EnemyEventKindEngaged, unitID))
		before := events(event("Ashfang", dnd.EnemyEventKindEngaged, unitID))
		result, err := validator.Validate(context.Background(), contracts.TypedValidationRequest[dnd.EnemyEventList]{Value: value})
		if err != nil || !result.Approved {
			t.Fatalf("separate scene validation = %#v, %v; want approved", result, err)
		}
		if !reflect.DeepEqual(value, before) {
			t.Fatalf("Validate() mutated separate scene value: got %#v, want %#v", value, before)
		}
	}
}

func TestValidatorRegistersStrictOptionsAndPolicy(t *testing.T) {
	registry := pipeline.NewValidatorRegistry()
	if err := Register(registry); err != nil {
		t.Fatal(err)
	}
	if Spec().Key != Key || Spec().ExecutionClass != contracts.ExecutionClassDeterministic {
		t.Fatalf("Spec() = %#v", Spec())
	}
	if got := New(Options{}).CheckpointFingerprints(); len(got) != 1 || got[0].Name != "policy" || got[0].Value != policy {
		t.Fatalf("CheckpointFingerprints() = %#v", got)
	}
	if _, err := DecodeOptions(map[string]any{"unexpected": true}); err == nil || !strings.Contains(err.Error(), "unknown option") {
		t.Fatalf("DecodeOptions() error = %v, want strict option rejection", err)
	}
}

func TestValidatorReportsEveryDuplicateSubjectWithContext(t *testing.T) {
	value := events(
		event("Ashfang", dnd.EnemyEventKindEngaged, 1),
		event("Ashfang", dnd.EnemyEventKindEngaged, 2),
		event("Briar", dnd.EnemyEventKindEngaged, 3),
		event("Briar", dnd.EnemyEventKindEngaged, 4),
	)
	result, err := New(Options{}).Validate(context.Background(), contracts.TypedValidationRequest[dnd.EnemyEventList]{Value: value})
	if err != nil || result.Approved {
		t.Fatalf("Validate() = %#v, %v; want rejection", result, err)
	}
	for _, want := range []string{"Ashfang", "Briar", "source unit 1", "source unit 2", "source unit 3", "source unit 4"} {
		if !strings.Contains(result.CorrectionGuidance, want) {
			t.Fatalf("CorrectionGuidance = %q, want %q", result.CorrectionGuidance, want)
		}
	}
	if strings.Contains(result.CorrectionGuidance, "events[") || strings.Contains(result.CorrectionGuidance, ReasonCode) {
		t.Fatalf("CorrectionGuidance exposed internal diagnostics: %q", result.CorrectionGuidance)
	}
}

func events(values ...dnd.EnemyEvent) dnd.EnemyEventList { return dnd.EnemyEventList{Events: values} }

func event(name string, kind dnd.EnemyEventKind, unitID int) dnd.EnemyEvent {
	return dnd.EnemyEvent{Name: name, Kind: kind, SourceRefs: refs(unitID)}
}

func refs(unitID int) []source.SourceRef {
	return []source.SourceRef{{SourceID: "session", StartUnitID: unitID, EndUnitID: unitID}}
}
