package workbench

import (
	"context"
	"os"
	"strings"
	"testing"
	"time"

	"aigateway.local/core/internal/platform/config"
	"aigateway.local/core/internal/platform/database"
)

// TestKnowledgeVectorLifecycle 跑真实 PostgreSQL(pgvector)+ Ollama:
// vector 模式知识库导入文档 → embedding 非空 → SemanticRetriever 语义命中;
// 并验证 embedder 失败时文档照常入库且发出 embedding_failed 事件。
// 需要 WORKBENCH_TEST_DATABASE_URL 与 WORKBENCH_TEST_OLLAMA_URL。
func TestKnowledgeVectorLifecycle(t *testing.T) {
	databaseURL := os.Getenv("WORKBENCH_TEST_DATABASE_URL")
	ollamaURL := os.Getenv("WORKBENCH_TEST_OLLAMA_URL")
	if databaseURL == "" || ollamaURL == "" {
		t.Skip("WORKBENCH_TEST_DATABASE_URL and WORKBENCH_TEST_OLLAMA_URL are not set")
	}
	ctx := context.Background()
	pool, err := database.Open(ctx, config.Database{URL: databaseURL, MaxConns: 8, MinConns: 0})
	if err != nil {
		t.Fatal(err)
	}
	defer pool.Close()
	actorID := "55555555-5555-4555-8555-555555555555"
	_, err = pool.Exec(ctx, `INSERT INTO gateway.admin_accounts(id,username,password_hash,role) VALUES($1,'m8-vector-test','test','superadmin') ON CONFLICT(id) DO NOTHING`, actorID)
	if err != nil {
		t.Fatal(err)
	}
	name := "m8-vector-integration-kb"
	cleanup := func() {
		_, _ = pool.Exec(ctx, `DELETE FROM gateway.knowledge_bases WHERE name=$1 OR name=$2`, name, name+"-fts")
	}
	cleanup()
	defer cleanup()

	assets := NewService(pool)
	assets.SetEmbedder(NewOllamaEmbedder(OllamaEmbedderConfig{BaseURL: ollamaURL, Model: "bge-m3", Dim: 1024, BatchSize: 8, Timeout: 60 * time.Second}))

	kb, err := assets.SaveKnowledgeBase(ctx, KnowledgeBase{Name: name, Description: "vector itest", RetrievalMode: "vector", ChunkSize: 300, ChunkOverlap: 40, DepartmentIDs: []string{}, Enabled: true}, actorID, true)
	if err != nil {
		t.Fatal(err)
	}
	doc, err := assets.AddKnowledgeDocument(ctx, kb.ID, "向量检索测试", "text", "", "pgvector 语义检索依赖 bge-m3 向量。Ollama 本地生成嵌入。", actorID)
	if err != nil {
		t.Fatal(err)
	}
	var embedded int
	if err = pool.QueryRow(ctx, `SELECT count(*) FROM gateway.knowledge_chunks WHERE document_id=$1 AND embedding IS NOT NULL`, doc.ID).Scan(&embedded); err != nil {
		t.Fatal(err)
	}
	if embedded == 0 || embedded != doc.ChunkCount {
		t.Fatalf("expected all chunks embedded, got %d/%d", embedded, doc.ChunkCount)
	}

	// 语义检索:查询词与正文无字面重合也应命中(余弦相似度)。
	hitChunks, err := (&SemanticRetriever{pool: pool, embedder: assets.Embedder()}).Search(ctx, kb.ID, "语义相似度匹配", 4)
	if err != nil {
		t.Fatalf("semantic search: %v", err)
	}
	if len(hitChunks) == 0 {
		t.Fatal("expected semantic hit")
	}
	if !strings.Contains(hitChunks[0].Content, "pgvector") {
		t.Fatalf("expected pgvector content in top hit, got %q", hitChunks[0].Content)
	}

	// 降级路径:embedder 失败 → 文档照常入库 + embedding_failed 事件。
	assets.SetEmbedder(failingEmbedder{})
	kbFts, err := assets.SaveKnowledgeBase(ctx, KnowledgeBase{Name: name + "-fts", Description: "degraded", RetrievalMode: "vector", ChunkSize: 300, ChunkOverlap: 40, DepartmentIDs: []string{}, Enabled: true}, actorID, true)
	if err != nil {
		t.Fatal(err)
	}
	degradedDoc, err := assets.AddKnowledgeDocument(ctx, kbFts.ID, "降级测试", "text", "", "没有向量也能入库。", actorID)
	if err != nil {
		t.Fatalf("degraded add should succeed, got %v", err)
	}
	var failed bool
	if err = pool.QueryRow(ctx, `SELECT EXISTS(SELECT 1 FROM gateway.outbox_events WHERE event_type='knowledge_document.embedding_failed' AND aggregate_id=$1)`, degradedDoc.ID).Scan(&failed); err != nil {
		t.Fatal(err)
	}
	if !failed {
		t.Fatal("expected embedding_failed event when embedder is down")
	}
}
