package memory

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

// 向量编解码往返：float32 打包进 bytea 再取回不失真。
func TestVecRoundTrip(t *testing.T) {
	v := []float32{0.1, -0.5, 1.0, 0, 0.333}
	got := decodeVec(encodeVec(v))
	if len(got) != len(v) {
		t.Fatalf("长度不符: %d vs %d", len(got), len(v))
	}
	for i := range v {
		if got[i] != v[i] {
			t.Errorf("第%d个失真: %v vs %v", i, got[i], v[i])
		}
	}
	// 脏数据（长度非 4 倍数）返回 nil，不 panic。
	if decodeVec([]byte{1, 2, 3}) != nil {
		t.Error("非法字节应返回 nil")
	}
	if decodeVec(nil) != nil {
		t.Error("空返回 nil")
	}
}

// cosine01：同向=1，正交=0.5→截到实际（正交余弦0→0），反向截到 0。
func TestCosine01(t *testing.T) {
	a := []float32{1, 0, 0}
	if c := cosine01(a, a); math.Abs(c-1) > 1e-6 {
		t.Errorf("同向应为 1，得 %v", c)
	}
	if c := cosine01(a, []float32{0, 1, 0}); c != 0 {
		t.Errorf("正交余弦 0，得 %v", c)
	}
	if c := cosine01(a, []float32{-1, 0, 0}); c != 0 {
		t.Errorf("反向应截到 0（不奖励相反语义），得 %v", c)
	}
	if c := cosine01(a, []float32{0, 0, 0}); c != 0 {
		t.Errorf("零向量应为 0，得 %v", c)
	}
}

// 三项打分模式（rel 非 nil）：相关性高的排到前面，即使重要度/最近性略低。
func TestRankProfiles_RelevanceMode(t *testing.T) {
	now := time.Now()
	rows := []Profile{
		{BaseModel: BaseModel{ID: "a"}, Key: "无关但重要", Importance: 10, LastSeenAt: now},
		{BaseModel: BaseModel{ID: "b"}, Key: "高度相关", Importance: 3, LastSeenAt: now},
	}
	rel := map[string]float64{"a": 0.05, "b": 0.95} // b 语义强相关

	// 两项模式（rel=nil）：a（重要度10）应在前。
	if got := rankProfiles(rows, now, 0, nil); got[0].ID != "a" {
		t.Errorf("两项模式重要度高者应在前，得 %s", got[0].ID)
	}
	// 三项模式：b 相关性 0.95 主导，应翻到前面。
	if got := rankProfiles(rows, now, 0, rel); got[0].ID != "b" {
		t.Errorf("三项模式相关性高者应在前，得 %s", got[0].ID)
	}
}

// relevance 优雅降级：无 embedder / query 空 → 返回 nil（不报错、不影响召回）。
func TestRelevance_GracefulFallback(t *testing.T) {
	s := &Store{} // 无 embedder
	if s.relevance(context.Background(), []Profile{{Key: "k"}}, "问题") != nil {
		t.Error("无 embedder 应返回 nil")
	}
	s.emb = stubEmbedder{}
	if s.relevance(context.Background(), []Profile{{Key: "k"}}, "  ") != nil {
		t.Error("query 空白应返回 nil（不白算嵌入）")
	}
}

// stubEmbedder 返回固定维度向量，供降级测试（不打真网络）。
type stubEmbedder struct{}

func (stubEmbedder) Embed(_ context.Context, _ []string) ([][]float32, error) {
	return [][]float32{{1, 0}}, nil
}
