package services

import (
	"fmt"
	"math"
	"sort"

	"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
)

// resultadoRAG empareja un chunk con su similitud a la consulta, para poder ordenar.
type resultadoRAG struct {
	chunk     models.UmindChunk
	similitud float64
}

// BuscarConocimiento embebe la consulta del usuario y devuelve los topK
// fragmentos más parecidos de la base de conocimiento del agente, por
// similitud coseno calculada en memoria. Sin pgvector por ahora: para el
// volumen de un piloto (un agente, unos cientos de chunks) esto es
// suficientemente rápido; si el volumen crece, se reemplaza por una consulta
// pgvector sin cambiar la firma de esta función.
func BuscarConocimiento(agenteID uint, consulta string, topK int) ([]models.UmindChunk, error) {
	if topK <= 0 {
		topK = 4
	}

	ai, err := models.GetUmindEmbeddingsConfig()
	if err != nil {
		return nil, err
	}
	consultaVec, err := GenerarEmbedding(ai, consulta)
	if err != nil {
		return nil, fmt.Errorf("no se pudo generar el embedding de la consulta: %w", err)
	}

	chunks, err := models.GetUmindChunksByAgente(agenteID)
	if err != nil {
		return nil, err
	}
	if len(chunks) == 0 {
		return nil, nil
	}

	resultados := make([]resultadoRAG, 0, len(chunks))
	for _, c := range chunks {
		vec, err := models.EmbeddingFromJSON(c.EmbeddingJSON)
		if err != nil || len(vec) == 0 {
			continue
		}
		resultados = append(resultados, resultadoRAG{chunk: c, similitud: similitudCoseno(consultaVec, vec)})
	}

	sort.Slice(resultados, func(i, j int) bool { return resultados[i].similitud > resultados[j].similitud })

	if topK > len(resultados) {
		topK = len(resultados)
	}
	out := make([]models.UmindChunk, topK)
	for i := 0; i < topK; i++ {
		out[i] = resultados[i].chunk
	}
	return out, nil
}

// similitudCoseno calcula la similitud coseno entre dos vectores del mismo
// tamaño. Vectores de tamaño distinto (embeddings de modelos diferentes) dan 0.
func similitudCoseno(a, b []float32) float64 {
	if len(a) != len(b) || len(a) == 0 {
		return 0
	}
	var punto, normaA, normaB float64
	for i := range a {
		ai, bi := float64(a[i]), float64(b[i])
		punto += ai * bi
		normaA += ai * ai
		normaB += bi * bi
	}
	if normaA == 0 || normaB == 0 {
		return 0
	}
	return punto / (math.Sqrt(normaA) * math.Sqrt(normaB))
}
