package store

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

// The write-path half of #493: recall of a fact must read back the fact, not
// the sentence he happened to say.
func TestFactRecallText(t *testing.T) {
	for _, tc := range []struct {
		name, key, value, want string
	}{
		{"json value", "go_version", `"1.20"`, "go version — 1.20"},
		{"plain value", "water", "выпил", "water — выпил"},
		{"no value", "shower", "", "shower"},
		{"underscores are spoken as spaces", "espresso_machine", `"чистая"`, "espresso machine — чистая"},
	} {
		t.Run(tc.name, func(t *testing.T) {
			if got := FactRecallText(tc.key, tc.value); got != tc.want {
				t.Fatalf("FactRecallText(%q, %q) = %q; want %q", tc.key, tc.value, got, tc.want)
			}
		})
	}
}

// A correction left the superseded value in the index, so recall answered with
// the value he had just corrected (#493).
func TestCorrectValueDropsMemoryVectors(t *testing.T) {
	ctx := context.Background()
	s := newTestStore(t)
	now := time.Now()
	mem := s.VectorMemory()

	if _, err := s.WriteFact(ctx, now, KindSelf, "go_version", `"1.20"`, "tap:voice", 1.0, sql.NullInt64{}); err != nil {
		t.Fatalf("WriteFact: %v", err)
	}
	if err := mem.Insert(ctx, "fact:go_version:1", []float32{1, 0, 0}, map[string]string{
		"type": "fact", "text": "go version — 1.20",
	}); err != nil {
		t.Fatalf("Insert: %v", err)
	}
	if _, err := s.CorrectValue(ctx, "go_version", "feedback", "1.25", now.Add(time.Minute)); err != nil {
		t.Fatalf("CorrectValue: %v", err)
	}
	got, err := mem.ByPrefix(ctx, "fact:")
	if err != nil {
		t.Fatalf("ByPrefix: %v", err)
	}
	if len(got) != 0 {
		t.Fatalf("after the correction the index still holds %+v; the superseded value must not answer", got)
	}
}

// The recovery path a poisoned box had none of (#470 point 4, #493): rows
// written before the fix hold utterances, voided junk and superseded values,
// and no write-path change reaches any of them.
func TestRepairFactVectors(t *testing.T) {
	ctx := context.Background()
	s := newTestStore(t)
	now := time.Now()
	mem := s.VectorMemory()
	embed := func(ctx context.Context, text string) ([]float32, error) {
		return []float32{float32(len(text)), 1, 0}, nil
	}

	// A live fact indexed under the question that wrote it — the defect.
	if _, err := s.WriteFact(ctx, now, KindSelf, "water", `"выпил"`, "tap:voice", 1.0, sql.NullInt64{}); err != nil {
		t.Fatalf("WriteFact water: %v", err)
	}
	if err := mem.Insert(ctx, "fact:water:100", []float32{9, 9, 9}, map[string]string{
		"type": "fact", "source": "voice", "text": "запиши что я пил воду",
	}); err != nil {
		t.Fatalf("Insert water: %v", err)
	}
	// A voided fact whose vector survived the void.
	if _, err := s.WriteFact(ctx, now, KindSelf, "go_version", `"1.20"`, "tap:voice", 1.0, sql.NullInt64{}); err != nil {
		t.Fatalf("WriteFact go_version: %v", err)
	}
	if _, _, err := s.VoidLatestFact(ctx, "go_version", "feedback", now.Add(time.Minute)); err != nil {
		t.Fatalf("VoidLatestFact: %v", err)
	}
	if err := mem.Insert(ctx, "fact:go_version:100", []float32{9, 9, 9}, map[string]string{
		"type": "fact", "text": "какая последняя версия языка Go?",
	}); err != nil {
		t.Fatalf("Insert go_version: %v", err)
	}
	// A key with two vectors: only the newest may answer.
	if _, err := s.WriteFact(ctx, now, KindSelf, "mood", `"устал"`, "tap:voice", 1.0, sql.NullInt64{}); err != nil {
		t.Fatalf("WriteFact mood: %v", err)
	}
	for _, ts := range []string{"100", "200"} {
		if err := mem.Insert(ctx, "fact:mood:"+ts, []float32{9, 9, 9}, map[string]string{
			"type": "fact", "text": "мне грустно",
		}); err != nil {
			t.Fatalf("Insert mood %s: %v", ts, err)
		}
	}
	// A note must be left entirely alone.
	if err := mem.Insert(ctx, "note:7", []float32{5, 5, 5}, map[string]string{
		"type": "note", "text": "сеть тормозит по вечерам",
	}); err != nil {
		t.Fatalf("Insert note: %v", err)
	}

	res, err := s.RepairFactVectors(ctx, embed)
	if err != nil {
		t.Fatalf("RepairFactVectors: %v", err)
	}
	if res.Rewritten != 2 || res.Dropped != 2 {
		t.Fatalf("repair reported %+v; want 2 rewritten (water, newest mood) and 2 dropped (voided go_version, superseded mood)", res)
	}

	got, err := mem.ByPrefix(ctx, "fact:")
	if err != nil {
		t.Fatalf("ByPrefix: %v", err)
	}
	texts := map[string]string{}
	for _, r := range got {
		texts[r.ID] = r.Meta["text"]
	}
	if len(texts) != 2 {
		t.Fatalf("the index holds %+v; want only fact:water:100 and fact:mood:200", texts)
	}
	if texts["fact:water:100"] != "water — выпил" {
		t.Fatalf("water reads back %q; want the fact, not the utterance", texts["fact:water:100"])
	}
	if texts["fact:mood:200"] != "mood — устал" {
		t.Fatalf("mood reads back %q", texts["fact:mood:200"])
	}
	// Provenance the row already carried must survive the rewrite.
	for _, r := range got {
		if r.ID == "fact:water:100" && r.Meta["source"] != "voice" {
			t.Fatalf("water lost its source meta: %+v", r.Meta)
		}
	}
	if notes, err := mem.ByPrefix(ctx, "note:"); err != nil || len(notes) != 1 {
		t.Fatalf("the note row was touched: %+v (err %v)", notes, err)
	}

	// Marker written, so a second run is free and changes nothing.
	again, err := s.RepairFactVectors(ctx, embed)
	if err != nil {
		t.Fatalf("second RepairFactVectors: %v", err)
	}
	if !again.Skipped {
		t.Fatalf("second run did work: %+v; the marker must make it a no-op", again)
	}
}
