package sourcerefs

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 TestValidatorOwnsCurrentSourceUnitAndRangeValidation(t *testing.T) {
	value := validList()
	result, err := New(Options{}).Validate(context.Background(), request(document(), value))
	if err != nil || !result.Approved {
		t.Fatalf("valid result = %#v, %v", result, err)
	}
	value.Occurrences[0].SourceRefs = []source.SourceRef{
		{SourceID: "foreign", StartUnitID: 1, EndUnitID: 1},
		{SourceID: "session", StartUnitID: 99, EndUnitID: 99},
		{SourceID: "session", StartUnitID: 2, EndUnitID: 1},
	}
	result, err = New(Options{}).Validate(context.Background(), request(document(), value))
	if err != nil || result.Approved || result.ReasonCode != ReasonCode || !strings.Contains(result.Message, "occurrences[0].source_refs[0]") {
		t.Fatalf("invalid result = %#v, %v", result, err)
	}
}

func TestValidatorRejectsDocumentValidEvidenceOutsideCurrentExtractionChunk(t *testing.T) {
	doc := document()
	chunk := &source.Chunk{
		ID:       "chunk-0",
		SourceID: doc.ID,
		Units:    append([]source.SourceUnit(nil), doc.Units[:2]...),
	}
	value := validList()
	result, err := New(Options{}).Validate(context.Background(), contracts.TypedValidationRequest[dnd.NPCOccurrenceList]{
		Stage:  string(pipeline.StageExtract),
		Source: doc,
		Chunk:  chunk,
		Value:  value,
	})
	if err != nil || !result.Approved {
		t.Fatalf("contained evidence = %#v, %v", result, err)
	}

	value.Occurrences[0].SourceRefs = []source.SourceRef{{
		SourceID: doc.ID, StartUnitID: 2, EndUnitID: 3,
	}}
	result, err = New(Options{}).Validate(context.Background(), contracts.TypedValidationRequest[dnd.NPCOccurrenceList]{
		Stage:  string(pipeline.StageExtract),
		Source: doc,
		Chunk:  chunk,
		Value:  value,
	})
	if err != nil || result.Approved || !strings.Contains(result.Message, "outside the current extraction chunk") {
		t.Fatalf("out-of-chunk evidence = %#v, %v", result, err)
	}
}

func TestValidatorRequiresChunkDuringExtractValidation(t *testing.T) {
	_, err := New(Options{}).Validate(context.Background(), contracts.TypedValidationRequest[dnd.NPCOccurrenceList]{
		Stage:  string(pipeline.StageExtract),
		Source: document(),
		Value:  validList(),
	})
	if err == nil || !strings.Contains(err.Error(), "requires the current extraction chunk") {
		t.Fatalf("Validate() error = %v", err)
	}
}

func TestValidatorDefersShapeAndDoesNotMutate(t *testing.T) {
	malformed := dnd.NPCOccurrenceList{Occurrences: []dnd.NPCOccurrence{{NPCID: "npc:test", Name: "Mira Thorn"}}}
	result, err := New(Options{}).Validate(context.Background(), request(document(), malformed))
	if err != nil || !result.Approved {
		t.Fatalf("shape deferral = %#v, %v", result, err)
	}
	value := validList()
	before := value
	_, err = New(Options{}).Validate(context.Background(), request(document(), value))
	if err != nil || !reflect.DeepEqual(value, before) {
		t.Fatalf("Validate() mutated value: %#v", value)
	}
	registry := pipeline.NewValidatorRegistry()
	if err := Register(registry); err != nil {
		t.Fatal(err)
	}
	if got := New(Options{}).CheckpointFingerprints(); len(got) != 1 || got[0].Value != policy {
		t.Fatalf("CheckpointFingerprints() = %#v", got)
	}
}

func request(doc *source.SourceDocument, value dnd.NPCOccurrenceList) contracts.TypedValidationRequest[dnd.NPCOccurrenceList] {
	return contracts.TypedValidationRequest[dnd.NPCOccurrenceList]{Source: doc, Value: value}
}

func document() *source.SourceDocument {
	return &source.SourceDocument{ID: "session", Units: []source.SourceUnit{{ID: 1}, {ID: 2}, {ID: 3}}}
}

func validList() dnd.NPCOccurrenceList {
	return dnd.NPCOccurrenceList{Occurrences: []dnd.NPCOccurrence{{
		NPCID: "npc:test", Name: "Mira Thorn", Kind: dnd.NPCOccurrenceKindDialogue,
		SourceRefs: []source.SourceRef{{SourceID: "session", StartUnitID: 1, EndUnitID: 2}},
	}}}
}
