package shape

import (
	"context"
	"reflect"
	"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 TestValidatorRequiresSceneShapeAndExactlyOneExtractionRecord(t *testing.T) {
	valid := list(scene("one", dnd.SceneKindNarrative, "Arrival", "The party arrives.", 1, 1))
	validator := New(Options{})
	for _, test := range []struct {
		name     string
		value    dnd.SceneDescriptionList
		stage    string
		approved bool
	}{
		{name: "valid normalization", value: valid, approved: true},
		{name: "valid extraction", value: valid, stage: string(pipeline.StageExtract), approved: true},
		{name: "multiple extraction records", value: list(valid.Scenes[0], scene("two", dnd.SceneKindMeta, "Rules", "The table checks rules.", 2, 2)), stage: string(pipeline.StageExtract)},
		{name: "nil list", value: dnd.SceneDescriptionList{}},
		{name: "empty list", value: dnd.SceneDescriptionList{Scenes: []dnd.SceneDescription{}}},
		{name: "blank ID", value: list(scene(" ", dnd.SceneKindNarrative, "Arrival", "The party arrives.", 1, 1))},
		{name: "unsupported kind", value: list(scene("one", "other", "Arrival", "The party arrives.", 1, 1))},
		{name: "empty title", value: list(scene("one", dnd.SceneKindNarrative, "", "The party arrives.", 1, 1))},
		{name: "whitespace-only title", value: list(scene("one", dnd.SceneKindNarrative, " ", "The party arrives.", 1, 1))},
		{name: "empty summary", value: list(scene("one", dnd.SceneKindNarrative, "Arrival", "", 1, 1))},
		{name: "whitespace-only summary", value: list(scene("one", dnd.SceneKindNarrative, "Arrival", " ", 1, 1))},
		{name: "untrimmed prose", value: list(scene("one", dnd.SceneKindNarrative, " Arrival ", "The party arrives.", 1, 1))},
		{name: "empty source reference", value: dnd.SceneDescriptionList{Scenes: []dnd.SceneDescription{{ID: "one", Kind: dnd.SceneKindNarrative, Title: "Arrival", Summary: "The party arrives."}}}},
	} {
		t.Run(test.name, func(t *testing.T) {
			result, err := validator.Validate(context.Background(), contracts.TypedValidationRequest[dnd.SceneDescriptionList]{Stage: test.stage, Value: test.value})
			if err != nil || result.Approved != test.approved {
				t.Fatalf("Validate() = %#v, %v; want approved=%t", result, err, test.approved)
			}
		})
	}
}

func TestValidatorContractAndRegistration(t *testing.T) {
	registry := pipeline.NewValidatorRegistry()
	if err := Register(registry); err != nil {
		t.Fatalf("Register() error = %v", err)
	}
	if _, ok := registry.Spec(Key); !ok {
		t.Fatalf("registry has no spec for %q", Key)
	}
	if _, err := DecodeOptions(map[string]any{"unexpected": true}); err == nil {
		t.Fatal("DecodeOptions() accepted unknown options")
	}
	validator := New(Options{})
	if got, want := validator.CheckpointFingerprints(), []pipeline.CheckpointFingerprint{{Name: "policy", Value: policy}}; !reflect.DeepEqual(got, want) {
		t.Fatalf("fingerprints = %#v, want %#v", got, want)
	}
}

func list(scenes ...dnd.SceneDescription) dnd.SceneDescriptionList {
	return dnd.SceneDescriptionList{Scenes: scenes}
}

func scene(id string, kind dnd.SceneKind, title, summary string, start, end int) dnd.SceneDescription {
	return dnd.SceneDescription{ID: id, Kind: kind, Title: title, Summary: summary, SourceRef: source.SourceRef{SourceID: "session", StartUnitID: start, EndUnitID: end}}
}
