package memory

import (
	"context"
	"hash/fnv"
	"math"
	"os"
	"strings"
	"testing"
)

// detEmbedder：确定性伪嵌入(同文本同向量;含指定关键词的文本在对应维度更高)。
// 只为端到端验证「本包代码路径」——Upsert 存向量 → Get 算余弦重排 —— 对真 PG，
// 不打真 embedding 网络(那是 rag 包已验证的复用基建)。
type detEmbedder struct{}

func (detEmbedder) Embed(_ context.Context, texts []string) ([][]float32, error) {
	const dim = 16
	kw := []string{"咖啡", "coffee", "运动", "健身", "音乐"}
	out := make([][]float32, len(texts))
	for i, t := range texts {
		v := make([]float32, dim)
		for _, w := range kw {
			if strings.Contains(t, w) {
				h := fnv.New32a()
				_, _ = h.Write([]byte(w))
				v[h.Sum32()%dim] += 1
			}
		}
		var n float64
		for _, x := range v {
			n += float64(x) * float64(x)
		}
		if n > 0 {
			for j := range v {
				v[j] = float32(float64(v[j]) / math.Sqrt(n))
			}
		}
		out[i] = v
	}
	return out, nil
}

// 端到端(真 PG)：写两条记忆(咖啡 importance=3 / 运动 importance=8)→
// 无 query 时运动(重要度高)在前；query="咖啡"时咖啡(语义相关)翻到前面。
func TestRelevance_EndToEnd(t *testing.T) {
	dsn := os.Getenv("MEMORY_TEST_DSN")
	if dsn == "" {
		t.Skip("设 MEMORY_TEST_DSN 启用 Postgres 端到端测试")
	}
	s := Open(dsn)
	if s.db == nil {
		t.Fatal("Store 不应降级")
	}
	s.SetEmbedder(detEmbedder{})
	ctx := context.Background()
	uid := "memtest-relevance-e2e"
	defer func() {
		_ = s.Delete(ctx, uid, "饮品偏好")
		_ = s.Delete(ctx, uid, "运动习惯")
	}()

	if err := s.Upsert(ctx, uid, "饮品偏好", "喜欢手冲咖啡 coffee 不加糖", 3); err != nil {
		t.Fatalf("upsert 咖啡: %v", err)
	}
	if err := s.Upsert(ctx, uid, "运动习惯", "每天健身运动一小时", 8); err != nil {
		t.Fatalf("upsert 运动: %v", err)
	}

	// 确认写入即向量化：embedding 列非空。
	var rows []Profile
	s.db.WithContext(ctx).Where("user_id = ?", uid).Find(&rows)
	for _, r := range rows {
		if len(r.Embedding) == 0 {
			t.Errorf("%s 应已向量化(embedding 非空)", r.Key)
		}
	}

	// 无 query：两项打分，运动(importance 8)在前。
	noQ, _ := s.Get(ctx, uid, "")
	if !strings.HasPrefix(noQ, "- 运动习惯") {
		t.Errorf("无 query 应重要度优先(运动在前)，得:\n%s", noQ)
	}
	// query 咖啡：三项打分，咖啡语义相关翻到前面(尽管重要度更低)。
	withQ, _ := s.Get(ctx, uid, "推荐一款好喝的咖啡 coffee")
	if !strings.HasPrefix(withQ, "- 饮品偏好") {
		t.Errorf("query=咖啡 应相关性优先(咖啡在前)，得:\n%s", withQ)
	}
}
