package sqlite

import (
	"context"
	"encoding/json"
	"strings"

	"chat-switchboard/models"
)

// RecallHybrid returns active memories using vector similarity + keyword search.
// SQLite has no pgvector, so we load embeddings and compute cosine similarity in Go.
func (s *MemoryStore) RecallHybrid(ctx context.Context, userID string, personaID *string, query string, queryVec []float64, limit int) ([]models.Memory, error) {
	if len(queryVec) == 0 {
		return s.Recall(ctx, userID, personaID, query, limit)
	}
	if limit <= 0 || limit > 100 {
		limit = 30
	}

	// Load all active memories with embeddings for applicable scopes
	semantic, err := s.recallSemantic(ctx, userID, personaID, queryVec, limit)
	if err != nil || len(semantic) == 0 {
		return s.Recall(ctx, userID, personaID, query, limit)
	}

	var keyword []models.Memory
	if query != "" {
		keyword, _ = s.Recall(ctx, userID, personaID, query, limit)
	}

	return mergeResultsSQLite(semantic, keyword, limit), nil
}

type scoredMemory struct {
	Memory     models.Memory
	Similarity float64
}

func (s *MemoryStore) recallSemantic(ctx context.Context, userID string, personaID *string, queryVec []float64, limit int) ([]models.Memory, error) {
	// Build query to load memories with embeddings
	parts := []string{}
	args := []interface{}{}

	userPart := `
		SELECT id, scope, owner_id, user_id, key, value,
		       source_channel_id, confidence, status, created_at, updated_at, embedding
		FROM memories
		WHERE scope = 'user' AND owner_id = ? AND status = 'active'
		  AND embedding IS NOT NULL
	`
	args = append(args, userID)
	parts = append(parts, userPart)

	if personaID != nil && *personaID != "" {
		personaPart := `
			SELECT id, scope, owner_id, user_id, key, value,
			       source_channel_id, confidence, status, created_at, updated_at, embedding
			FROM memories
			WHERE scope = 'persona' AND owner_id = ? AND status = 'active'
			  AND embedding IS NOT NULL
		`
		args = append(args, *personaID)
		parts = append(parts, personaPart)

		puPart := `
			SELECT id, scope, owner_id, user_id, key, value,
			       source_channel_id, confidence, status, created_at, updated_at, embedding
			FROM memories
			WHERE scope = 'persona_user' AND owner_id = ? AND user_id = ? AND status = 'active'
			  AND embedding IS NOT NULL
		`
		args = append(args, *personaID, userID)
		parts = append(parts, puPart)
	}

	fullQuery := strings.Join(parts, " UNION ALL ")
	rows, err := DB.QueryContext(ctx, fullQuery, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	// Score each memory by cosine similarity
	var scored []scoredMemory
	for rows.Next() {
		var m models.Memory
		var embeddingJSON string
		if err := rows.Scan(
			&m.ID, &m.Scope, &m.OwnerID, &m.UserID, &m.Key, &m.Value,
			&m.SourceChannelID, &m.Confidence, &m.Status,
			st(&m.CreatedAt), st(&m.UpdatedAt), &embeddingJSON,
		); err != nil {
			continue
		}

		var vec []float64
		if err := json.Unmarshal([]byte(embeddingJSON), &vec); err != nil {
			continue
		}

		// cosineSimilarity is defined in knowledge_bases.go (same package)
		sim := cosineSimilarity(queryVec, vec)
		if sim > 0.3 {
			scored = append(scored, scoredMemory{Memory: m, Similarity: sim})
		}
	}

	// Sort by similarity descending (simple insertion sort for small N)
	for i := 0; i < len(scored); i++ {
		for j := i + 1; j < len(scored); j++ {
			if scored[j].Similarity > scored[i].Similarity {
				scored[i], scored[j] = scored[j], scored[i]
			}
		}
	}

	// Take top N
	result := make([]models.Memory, 0, limit)
	for i := 0; i < len(scored) && i < limit; i++ {
		result = append(result, scored[i].Memory)
	}
	return result, rows.Err()
}

func mergeResultsSQLite(semantic, keyword []models.Memory, limit int) []models.Memory {
	seen := make(map[string]bool, len(semantic))
	result := make([]models.Memory, 0, limit)

	for _, m := range semantic {
		if len(result) >= limit {
			break
		}
		seen[m.ID] = true
		result = append(result, m)
	}
	for _, m := range keyword {
		if len(result) >= limit {
			break
		}
		if !seen[m.ID] {
			result = append(result, m)
		}
	}
	return result
}
