package store

import (
	"context"
	"testing"
	"time"
)

func TestQueryNotesRanksByCosine(t *testing.T) {
	ctx := context.Background()
	st := newTestStore(t)

	now := time.Now()
	// 3-dim vectors along distinct axes; query aligns with the "backups" note.
	if _, err := st.WriteNote(ctx, now, "prefer backups at 3am", []float32{1, 0, 0}, "tap:voice"); err != nil {
		t.Fatal(err)
	}
	if _, err := st.WriteNote(ctx, now, "gpu driver fixed the flicker", []float32{0, 1, 0}, "tap:voice"); err != nil {
		t.Fatal(err)
	}
	if _, err := st.WriteNote(ctx, now, "cat likes the window", []float32{0, 0, 1}, "tap:voice"); err != nil {
		t.Fatal(err)
	}

	got, err := st.QueryNotes(ctx, []float32{0.9, 0.1, 0}, 2)
	if err != nil {
		t.Fatal(err)
	}
	if len(got) != 2 {
		t.Fatalf("want 2 notes, got %d", len(got))
	}
	if got[0].Text != "prefer backups at 3am" {
		t.Errorf("nearest = %q, want backups note (score %.3f)", got[0].Text, got[0].Score)
	}
	if got[0].Score <= got[1].Score {
		t.Errorf("scores not descending: %.3f then %.3f", got[0].Score, got[1].Score)
	}
}

// Recall answers questions about HIM. A feed item and a crawled page are text
// Maven read somewhere, and letting them into the nearest-neighbour pool means
// "что я говорил про переезд" can be answered with a stranger's sentence, under
// the line "вот что я нашла: ".
func TestQueryNotesLeavesOutWhatSheOnlyRead(t *testing.T) {
	ctx := context.Background()
	st := newTestStore(t)
	now := time.Now()
	// The read sources sit exactly on the query vector; his own note is further
	// away, so ranking alone would put them first.
	for _, n := range []struct{ text, source string }{
		{"переезд в новую квартиру описан тут", "rss:habr"},
		{"страница про переезд", "crawl:changelog"},
	} {
		if _, err := st.WriteNote(ctx, now, n.text, []float32{1, 0, 0}, n.source); err != nil {
			t.Fatal(err)
		}
	}
	if _, err := st.WriteNote(ctx, now, "переезд в субботу", []float32{0.8, 0.6, 0}, "tap:voice"); err != nil {
		t.Fatal(err)
	}

	got, err := st.QueryNotes(ctx, []float32{1, 0, 0}, 5)
	if err != nil {
		t.Fatal(err)
	}
	if len(got) != 1 || got[0].Source != "tap:voice" {
		t.Fatalf("recall returned %+v; want only what he said himself", got)
	}

	// They are still reachable, by source.
	feed, err := st.RecentNotesFromSource(ctx, "rss:", 10)
	if err != nil {
		t.Fatal(err)
	}
	if len(feed) != 1 || feed[0].Source != "rss:habr" {
		t.Fatalf("RecentNotesFromSource(rss:) = %+v; want the one feed note", feed)
	}
}
