package store

import (
	"context"
	"database/sql"
	"testing"
	"time"
)

func TestWriteFactAboutSubject_NoSubjectStaysNone(t *testing.T) {
	s := newTestStore(t)
	ctx := context.Background()

	id, err := s.WriteFactAboutSubject(ctx, time.Now(), KindSelf, "mood", "", `"content"`, "tap:mood", 1.0, sql.NullInt64{})
	if err != nil {
		t.Fatalf("WriteFactAboutSubject: %v", err)
	}

	pending, err := s.PendingFactResolutions(ctx, 10)
	if err != nil {
		t.Fatalf("PendingFactResolutions: %v", err)
	}
	for _, f := range pending {
		if f.ID == id {
			t.Fatalf("fact %d written with no subject should not be queued for resolution", id)
		}
	}
}

func TestWriteFactAboutSubject_QueuesPendingResolution(t *testing.T) {
	s := newTestStore(t)
	ctx := context.Background()

	id, err := s.WriteFactAboutSubject(ctx, time.Now(), KindEnv, "likes", "the espresso machine", `"true"`, "infer:pref", 0.8, sql.NullInt64{})
	if err != nil {
		t.Fatalf("WriteFactAboutSubject: %v", err)
	}

	pending, err := s.PendingFactResolutions(ctx, 10)
	if err != nil {
		t.Fatalf("PendingFactResolutions: %v", err)
	}
	if len(pending) != 1 || pending[0].ID != id {
		t.Fatalf("expected fact %d in pending queue, got %+v", id, pending)
	}
	if pending[0].Subject != "the espresso machine" {
		t.Fatalf("expected subject preserved, got %q", pending[0].Subject)
	}
	if pending[0].ResolutionState != ResolutionPending {
		t.Fatalf("expected ResolutionPending, got %q", pending[0].ResolutionState)
	}
}

func TestResolveFactEntity_ResolvedMakesItFindableByEntity(t *testing.T) {
	s := newTestStore(t)
	ctx := context.Background()

	id, err := s.WriteFactAboutSubject(ctx, time.Now(), KindEnv, "likes", "the espresso machine", `"true"`, "infer:pref", 0.8, sql.NullInt64{})
	if err != nil {
		t.Fatalf("WriteFactAboutSubject: %v", err)
	}

	if err := s.ResolveFactEntity(ctx, id, "ent_espresso", ResolutionResolved); err != nil {
		t.Fatalf("ResolveFactEntity: %v", err)
	}

	pending, err := s.PendingFactResolutions(ctx, 10)
	if err != nil {
		t.Fatalf("PendingFactResolutions: %v", err)
	}
	if len(pending) != 0 {
		t.Fatalf("expected resolved fact to leave the pending queue, got %+v", pending)
	}

	facts, err := s.FactsByEntity(ctx, "ent_espresso", 10)
	if err != nil {
		t.Fatalf("FactsByEntity: %v", err)
	}
	if len(facts) != 1 || facts[0].ID != id {
		t.Fatalf("expected fact %d under ent_espresso, got %+v", id, facts)
	}
	if !facts[0].EntityID.Valid || facts[0].EntityID.String != "ent_espresso" {
		t.Fatalf("expected EntityID set, got %+v", facts[0].EntityID)
	}
}

// TestResolveFactEntity_AmbiguousDoesNotStoreAnEntityID covers the
// ecosystem-wide invariant that ambiguity blocks mutation: an ambiguous
// resolve must never guess an entity_id, even transiently.
func TestResolveFactEntity_AmbiguousDoesNotStoreAnEntityID(t *testing.T) {
	s := newTestStore(t)
	ctx := context.Background()

	id, err := s.WriteFactAboutSubject(ctx, time.Now(), KindEnv, "likes", "kate", `"true"`, "infer:pref", 0.8, sql.NullInt64{})
	if err != nil {
		t.Fatalf("WriteFactAboutSubject: %v", err)
	}

	// Simulate an ambiguous Nexus response: candidates found, but no single
	// entity_id — the enrichment worker passes "" for entityID in this case.
	if err := s.ResolveFactEntity(ctx, id, "", ResolutionAmbiguous); err != nil {
		t.Fatalf("ResolveFactEntity: %v", err)
	}

	pending, err := s.PendingFactResolutions(ctx, 10)
	if err != nil {
		t.Fatalf("PendingFactResolutions: %v", err)
	}
	if len(pending) != 0 {
		t.Fatalf("ambiguous fact should leave the pending queue (it's terminal), got %+v", pending)
	}
}
