package main

import (
	"context"
	"errors"
	"log/slog"
	"sync"
	"time"

	"fiko-ai/config"
	"fiko-ai/internal/agent"
	"fiko-ai/internal/knowledge"
	"fiko-ai/internal/llm"
)

// setupKnowledgeVectorSearch MongoDB $vectorSearch'ü hazırlar: embedding boyutunu bir sonda
// çağrıyla ölçer, vektör index'ini kurar/günceller ve sorgulanabilir olmasını bekler.
// Hepsi ARKA PLANDADIR: sunucu açılışı beklemez; index hazır olana kadar (ya da mongot yoksa hep)
// arama Go tarafı skorlamayla çalışır. Dönen fonksiyon işi iptal edip bitmesini bekler
// (Mongo bağlantısı kapanırken yarım komut kalmasın diye `defer` ile çağrılmalıdır).
//
// KNOWLEDGE_VECTOR_SEARCH: "off" hiç denemez; "auto" kullanılamıyorsa bilgi düzeyinde söyler;
// "on" kullanılamıyorsa UYARIR (yine de çökmez, Go tarafına düşer).
func setupKnowledgeVectorSearch(ctx context.Context, repo knowledge.Repository, emb llm.Embedder, cfg config.Config, log *slog.Logger) func() {
	noop := func() {}
	mode := cfg.Knowledge.VectorSearch
	if mode == "" {
		mode = "auto"
	}
	if mode == "off" {
		log.Info("bilgi tabanı vektör arama kapalı (KNOWLEDGE_VECTOR_SEARCH=off): skorlama Go tarafında")
		return noop
	}
	idx, ok := repo.(knowledge.VectorIndexer)
	if !ok {
		// Bellek içi depo: $vectorSearch yok. Beklenen durum; Mongo yokken uyarı gürültüsü yapma.
		log.Debug("depo vektör aramayı desteklemiyor; skorlama Go tarafında")
		return noop
	}
	if cfg.Ollama.EmbedModel == "" || emb == nil {
		log.Info("bilgi tabanı vektör arama atlandı: embedding kapalı (OLLAMA_EMBED_MODEL boş)")
		return noop
	}

	timeout := cfg.Knowledge.VectorIndexTimeout
	if timeout <= 0 {
		timeout = 10 * time.Minute
	}
	jctx, cancel := context.WithTimeout(ctx, timeout)
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()
		start := time.Now()

		// Boyut modele bağlı (qwen3-embedding varyantları 1024/2560/4096...): koda gömmek yerine ölç.
		pctx, pcancel := context.WithTimeout(jctx, prewarmTimeout)
		probe, err := emb.Embed(pctx, "fiko hazır")
		pcancel()
		if err != nil || len(probe) == 0 {
			if jctx.Err() == nil {
				log.Warn("vektör index'i için embedding boyutu ölçülemedi; arama Go tarafı skorlamada kalıyor",
					"err", err)
			}
			return
		}

		if err := idx.PrepareVectorIndex(jctx, len(probe)); err != nil {
			if jctx.Err() != nil && ctx.Err() != nil {
				return // kapanış
			}
			if errors.Is(err, knowledge.ErrVectorUnavailable) && mode == "auto" {
				log.Info("MongoDB $vectorSearch kullanılamıyor (mongot yok ya da bağlı değil): skorlama Go tarafında",
					"neden", err)
				return
			}
			log.Warn("MongoDB vektör index'i hazırlanamadı: skorlama Go tarafında (KNOWLEDGE_VECTOR_SEARCH=off ile sessize al)",
				"err", err)
			return
		}
		log.Info("MongoDB $vectorSearch ETKİN", "index", "kb_chunk_vector", "boyut", len(probe),
			"sure", time.Since(start).Round(time.Millisecond))
	}()
	return func() {
		cancel()
		wg.Wait()
	}
}

// knowledgeMinScore RAG'e girecek en düşük hibrit skor. Eşik ilgisiz belgenin isteme sızmasını
// önler: bilgi tabanı doluyken "merhaba" gibi mesajlarda bile en yakın parça dönerdi.
//
// DİKKAT: 0.35 bir İLK TAHMİNDİR, gerçek veriyle ayarlanmadı. Skor = %80 kosinüs + %20 anahtar
// kelime; embedding yokken yalnızca anahtar kelime oranıdır. Canlı belgelerle ölçüp ayarlanmalı
// (log: "bilgi tabanından alıntı eklendi" hangi belgelerin girdiğini yazar).
const knowledgeMinScore = 0.35

// knowledgeRetriever knowledge.Service'i agent.KnowledgeRetriever arayüzüne uyarlar.
// Ajan knowledge paketini bilmez (skillRetriever ile aynı desen).
type knowledgeRetriever struct {
	svc      *knowledge.Service
	minScore float64
}

func (k knowledgeRetriever) Passages(ctx context.Context, userID, query string, limit int) []agent.Passage {
	// Boş bilgi tabanında sorgu embedding'i hiç üretilmez: ses gecikmesine ek yük binmez.
	if !k.svc.HasDocuments(ctx, userID) {
		return nil
	}
	hits, err := k.svc.Search(ctx, knowledge.SearchOptions{
		UserID: userID, Query: query, Mode: knowledge.ModeHybrid, Limit: limit, MinScore: k.minScore,
	})
	if err != nil || len(hits) == 0 {
		return nil // getirme üretimi asla engellemez (sözleşme)
	}
	out := make([]agent.Passage, 0, len(hits))
	for _, h := range hits {
		out = append(out, agent.Passage{Title: h.Title, Source: h.Source, Text: h.Text, Score: h.Score})
	}
	return out
}
