package shared

import (
	"reflect"
	"testing"

	"gitea.maximumdirect.net/eric/notarius/internal/core/source"
)

func TestSourceRefOrderUsesDocumentOrderAndLiteralFallbacks(t *testing.T) {
	doc := unitRefSourceDocument(30, 10, 20)
	order := NewSourceRefOrder(doc)

	refs := []source.SourceRef{
		{SourceID: doc.ID, StartUnitID: 10, EndUnitID: 10},
		{SourceID: doc.ID, StartUnitID: 999, EndUnitID: 999},
		{SourceID: doc.ID, StartUnitID: 30, EndUnitID: 30},
		{SourceID: "other", StartUnitID: 30, EndUnitID: 1},
		{SourceID: "other", StartUnitID: 10, EndUnitID: 2},
	}
	got := order.Canonicalize(refs)
	want := []source.SourceRef{
		{SourceID: "other", StartUnitID: 10, EndUnitID: 2},
		{SourceID: "other", StartUnitID: 30, EndUnitID: 1},
		{SourceID: doc.ID, StartUnitID: 30, EndUnitID: 30},
		{SourceID: doc.ID, StartUnitID: 10, EndUnitID: 10},
		{SourceID: doc.ID, StartUnitID: 999, EndUnitID: 999},
	}
	if !reflect.DeepEqual(got, want) {
		t.Fatalf("Canonicalize() = %#v, want %#v", got, want)
	}
	if order.Less(want[0], want[0]) {
		t.Fatal("Less(ref, ref) = true, want false")
	}
}

func TestSourceRefOrderCanonicalizePreservesRepresentationAndOwnership(t *testing.T) {
	order := NewSourceRefOrder(unitRefSourceDocument(3, 1, 2))
	if got := order.Canonicalize(nil); got != nil {
		t.Fatalf("Canonicalize(nil) = %#v, want nil", got)
	}
	empty := []source.SourceRef{}
	if got := order.Canonicalize(empty); got == nil || len(got) != 0 {
		t.Fatalf("Canonicalize(empty) = %#v, want owned empty slice", got)
	}
	input := []source.SourceRef{
		{SourceID: "session-alpha", StartUnitID: 1, EndUnitID: 2},
		{SourceID: "session-alpha", StartUnitID: 1, EndUnitID: 2},
		{SourceID: "session-alpha", StartUnitID: 1, EndUnitID: 3},
	}
	got := order.Canonicalize(input)
	if len(got) != 2 || got[0] == got[1] {
		t.Fatalf("Canonicalize() = %#v, want exact duplicate removed but distinct range retained", got)
	}
	got[0].StartUnitID = 99
	if input[0].StartUnitID == 99 {
		t.Fatal("Canonicalize() output aliases input")
	}
}

func TestSourceRefOrderOrdersOnlyResolvableCurrentSourceEndpoints(t *testing.T) {
	doc := unitRefSourceDocument(30, 10, 20)
	order := NewSourceRefOrder(doc)
	input := []source.SourceRef{
		{SourceID: doc.ID, StartUnitID: 30, EndUnitID: 10},
		{SourceID: doc.ID, StartUnitID: 20, EndUnitID: 30},
		{SourceID: doc.ID, StartUnitID: 10, EndUnitID: 10},
		{SourceID: "other", StartUnitID: 20, EndUnitID: 30},
		{SourceID: doc.ID, StartUnitID: 999, EndUnitID: 30},
		{SourceID: doc.ID, StartUnitID: 20, EndUnitID: 0},
	}
	want := []source.SourceRef{
		{SourceID: doc.ID, StartUnitID: 30, EndUnitID: 10},
		{SourceID: doc.ID, StartUnitID: 30, EndUnitID: 20},
		{SourceID: doc.ID, StartUnitID: 10, EndUnitID: 10},
		{SourceID: "other", StartUnitID: 20, EndUnitID: 30},
		{SourceID: doc.ID, StartUnitID: 999, EndUnitID: 30},
		{SourceID: doc.ID, StartUnitID: 20, EndUnitID: 0},
	}
	got := order.OrderEndpoints(input)
	if !reflect.DeepEqual(got, want) {
		t.Fatalf("OrderEndpoints() = %#v, want %#v", got, want)
	}
	got[0].StartUnitID = 999
	if input[0].StartUnitID != 30 {
		t.Fatal("OrderEndpoints() output aliases input")
	}
	if got := order.OrderEndpoints(nil); got != nil {
		t.Fatalf("OrderEndpoints(nil) = %#v, want nil", got)
	}
}

func TestSourceRefOrderSnapshotAndEarliestValid(t *testing.T) {
	doc := unitRefSourceDocument(30, 10, 20)
	sourceID := doc.ID
	index := source.NewDocumentIndex(doc)
	order := NewSourceRefOrderFromIndex(index)
	doc.ID = "changed"
	doc.Units[0].ID, doc.Units[1].ID = 10, 30
	refs := []source.SourceRef{
		{SourceID: sourceID, StartUnitID: 20, EndUnitID: 20},
		{SourceID: sourceID, StartUnitID: 10, EndUnitID: 10},
		{SourceID: "other", StartUnitID: 1, EndUnitID: 1},
		{SourceID: sourceID, StartUnitID: 999, EndUnitID: 999},
		{SourceID: sourceID, StartUnitID: 20, EndUnitID: 30},
	}
	if position, ok := order.EarliestValid(refs); !ok || position != 1 {
		t.Fatalf("EarliestValid() = %d, %t, want 1, true", position, ok)
	}
	if position, ok := order.EarliestValid([]source.SourceRef{{SourceID: sourceID, StartUnitID: 20, EndUnitID: 30}}); ok || position != 0 {
		t.Fatalf("EarliestValid(invalid) = %d, %t, want 0, false", position, ok)
	}
	if position, ok := (SourceRefOrder{}).EarliestValid(refs); ok || position != 0 {
		t.Fatalf("zero EarliestValid() = %d, %t, want 0, false", position, ok)
	}
}
