//! Gestionnaire de requêtes préparées pour optimiser les performances
//!
//! Ce module implémente un cache de requêtes préparées pour améliorer
//! les performances et la sécurité des requêtes SQL fréquentes.

package database

import (
	"context"
	"database/sql"
	"fmt"
	"sync"

	"go.uber.org/zap"
)

// PreparedStatement représente une requête préparée avec son nom
type PreparedStatement struct {
	Name  string
	Query string
	Stmt  *sql.Stmt
}

// PreparedStatementManager gère le cache des requêtes préparées
type PreparedStatementManager struct {
	db         *sql.DB
	statements map[string]*PreparedStatement
	mutex      sync.RWMutex
	logger     *zap.Logger
}

// NewPreparedStatementManager crée un nouveau gestionnaire de requêtes préparées
func NewPreparedStatementManager(db *sql.DB, logger *zap.Logger) *PreparedStatementManager {
	return &PreparedStatementManager{
		db:         db,
		statements: make(map[string]*PreparedStatement),
		logger:     logger,
	}
}

// Prepare prépare une requête SQL et la met en cache
func (psm *PreparedStatementManager) Prepare(ctx context.Context, name, query string) error {
	psm.mutex.Lock()
	defer psm.mutex.Unlock()

	// Vérifier si la requête est déjà préparée
	if _, exists := psm.statements[name]; exists {
		psm.logger.Debug("Statement already prepared", zap.String("name", name))
		return nil
	}

	// Préparer la requête
	stmt, err := psm.db.PrepareContext(ctx, query)
	if err != nil {
		psm.logger.Error("Failed to prepare statement",
			zap.String("name", name),
			zap.String("query", query),
			zap.Error(err))
		return fmt.Errorf("failed to prepare statement %s: %w", name, err)
	}

	// Mettre en cache
	psm.statements[name] = &PreparedStatement{
		Name:  name,
		Query: query,
		Stmt:  stmt,
	}

	psm.logger.Debug("Statement prepared successfully",
		zap.String("name", name))

	return nil
}

// GetStatement récupère une requête préparée depuis le cache
func (psm *PreparedStatementManager) GetStatement(name string) (*sql.Stmt, error) {
	psm.mutex.RLock()
	defer psm.mutex.RUnlock()

	stmt, exists := psm.statements[name]
	if !exists {
		return nil, fmt.Errorf("statement %s not found", name)
	}

	return stmt.Stmt, nil
}

// Execute exécute une requête préparée avec des arguments
func (psm *PreparedStatementManager) Execute(ctx context.Context, name string, args ...interface{}) (sql.Result, error) {
	stmt, err := psm.GetStatement(name)
	if err != nil {
		return nil, err
	}

	return stmt.ExecContext(ctx, args...)
}

// Query exécute une requête préparée et retourne des lignes
func (psm *PreparedStatementManager) Query(ctx context.Context, name string, args ...interface{}) (*sql.Rows, error) {
	stmt, err := psm.GetStatement(name)
	if err != nil {
		return nil, err
	}

	return stmt.QueryContext(ctx, args...)
}

// QueryRow exécute une requête préparée et retourne une ligne
func (psm *PreparedStatementManager) QueryRow(ctx context.Context, name string, args ...interface{}) *sql.Row {
	stmt, err := psm.GetStatement(name)
	if err != nil {
		// Retourner une erreur dans le Row
		return &sql.Row{}
	}

	return stmt.QueryRowContext(ctx, args...)
}

// Initialize prépare toutes les requêtes fréquemment utilisées
func (psm *PreparedStatementManager) Initialize(ctx context.Context) error {
	psm.logger.Info("Initializing prepared statements...")

	// Requêtes utilisateur
	statements := map[string]string{
		"get_user_by_id": `
			SELECT id, username, email, password_hash, created_at, updated_at, deleted_at
			FROM users WHERE id = $1 AND deleted_at IS NULL`,

		"get_user_by_email": `
			SELECT id, username, email, password_hash, created_at, updated_at, deleted_at
			FROM users WHERE email = $1 AND deleted_at IS NULL`,

		"get_user_by_username": `
			SELECT id, username, email, password_hash, created_at, updated_at, deleted_at
			FROM users WHERE username = $1 AND deleted_at IS NULL`,

		"create_user": `
			INSERT INTO users (username, email, password_hash, created_at, updated_at)
			VALUES ($1, $2, $3, $4, $5) RETURNING id`,

		"update_user": `
			UPDATE users SET username = $2, email = $3, updated_at = $4
			WHERE id = $1 AND deleted_at IS NULL`,

		"delete_user": `
			UPDATE users SET deleted_at = $2 WHERE id = $1`,

		// Requêtes de session
		"get_session_by_token": `
			SELECT id, user_id, token, created_at, expires_at, ip_address, user_agent, is_valid
			FROM sessions WHERE token = $1 AND expires_at > $2 AND is_valid = true`,

		"create_session": `
			INSERT INTO sessions (user_id, token, created_at, expires_at, ip_address, user_agent)
			VALUES ($1, $2, $3, $4, $5, $6) RETURNING id`,

		"revoke_session": `
			UPDATE sessions SET is_valid = false, revoked_at = $2 WHERE token = $1`,

		"revoke_user_sessions": `
			UPDATE sessions SET is_valid = false, revoked_at = $2 
			WHERE user_id = $1 AND is_valid = true`,

		"cleanup_expired_sessions": `
			DELETE FROM sessions WHERE expires_at < $1`,

		// Requêtes de messages
		"get_messages_by_room": `
			SELECT m.id, m.room_id, m.user_id, m.content, m.type, m.parent_id, 
			       m.is_edited, m.is_deleted, m.created_at, m.updated_at,
			       u.username, u.email
			FROM messages m
			JOIN users u ON m.user_id = u.id
			WHERE m.room_id = $1 AND m.created_at < $2
			ORDER BY m.created_at DESC LIMIT $3`,

		"create_message": `
			INSERT INTO messages (room_id, user_id, content, type, parent_id, created_at, updated_at)
			VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id`,

		"update_message": `
			UPDATE messages SET content = $2, is_edited = true, updated_at = $3
			WHERE id = $1 AND user_id = $4`,

		"delete_message": `
			UPDATE messages SET is_deleted = true, updated_at = $2 WHERE id = $1`,

		// Requêtes de tracks
		"get_track_by_id": `
			SELECT id, user_id, title, artist, duration, file_path, file_size, 
			       mime_type, status, created_at, updated_at
			FROM tracks WHERE id = $1 AND status = 'active'`,

		"get_user_tracks": `
			SELECT id, user_id, title, artist, duration, file_path, file_size,
			       mime_type, status, created_at, updated_at
			FROM tracks WHERE user_id = $1 AND created_at < $2 AND status = 'active'
			ORDER BY created_at DESC LIMIT $3`,

		"create_track": `
			INSERT INTO tracks (user_id, title, artist, duration, file_path, file_size, mime_type, status, created_at, updated_at)
			VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING id`,

		"update_track": `
			UPDATE tracks SET title = $2, artist = $3, updated_at = $4
			WHERE id = $1 AND user_id = $5`,

		"delete_track": `
			UPDATE tracks SET status = 'deleted', updated_at = $2 WHERE id = $1`,

		// Requêtes de rooms
		"get_room_by_id": `
			SELECT id, name, description, type, is_private, created_by, created_at, updated_at
			FROM rooms WHERE id = $1`,

		"get_user_rooms": `
			SELECT r.id, r.name, r.description, r.type, r.is_private, r.created_by, r.created_at, r.updated_at
			FROM rooms r
			JOIN room_users ru ON r.id = ru.room_id
			WHERE ru.user_id = $1 AND r.created_at < $2
			ORDER BY r.created_at DESC LIMIT $3`,

		"create_room": `
			INSERT INTO rooms (name, description, type, is_private, created_by, created_at, updated_at)
			VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id`,

		"add_user_to_room": `
			INSERT INTO room_users (room_id, user_id, created_at)
			VALUES ($1, $2, $3) ON CONFLICT (room_id, user_id) DO NOTHING`,

		"remove_user_from_room": `
			DELETE FROM room_users WHERE room_id = $1 AND user_id = $2`,

		// Requêtes d'audit
		"create_audit_log": `
			INSERT INTO audit_logs (user_id, action, entity_type, entity_id, ip_address, user_agent, details, created_at)
			VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id`,

		"get_audit_logs": `
			SELECT id, user_id, action, entity_type, entity_id, ip_address, user_agent, details, created_at
			FROM audit_logs WHERE user_id = $1 AND created_at < $2
			ORDER BY created_at DESC LIMIT $3`,

		// Requêtes de recherche
		"search_tracks": `
			SELECT id, user_id, title, artist, duration, file_path, file_size,
			       mime_type, status, created_at, updated_at,
			       ts_rank(to_tsvector('english', title || ' ' || artist), plainto_tsquery('english', $1)) as rank
			FROM tracks WHERE status = 'active' AND to_tsvector('english', title || ' ' || artist) @@ plainto_tsquery('english', $1)
			ORDER BY rank DESC, created_at DESC LIMIT $2`,

		"search_messages": `
			SELECT m.id, m.room_id, m.user_id, m.content, m.type, m.created_at,
			       u.username, u.email,
			       ts_rank(to_tsvector('english', m.content), plainto_tsquery('english', $1)) as rank
			FROM messages m
			JOIN users u ON m.user_id = u.id
			WHERE m.room_id = $2 AND to_tsvector('english', m.content) @@ plainto_tsquery('english', $1)
			ORDER BY rank DESC, m.created_at DESC LIMIT $3`,
	}

	// Préparer toutes les requêtes
	for name, query := range statements {
		if err := psm.Prepare(ctx, name, query); err != nil {
			psm.logger.Error("Failed to prepare statement",
				zap.String("name", name),
				zap.Error(err))
			return err
		}
	}

	psm.logger.Info("All prepared statements initialized successfully",
		zap.Int("count", len(statements)))

	return nil
}

// Close ferme toutes les requêtes préparées
func (psm *PreparedStatementManager) Close() error {
	psm.mutex.Lock()
	defer psm.mutex.Unlock()

	var lastErr error
	for name, stmt := range psm.statements {
		if err := stmt.Stmt.Close(); err != nil {
			psm.logger.Error("Failed to close statement",
				zap.String("name", name),
				zap.Error(err))
			lastErr = err
		}
	}

	// Vider le cache
	psm.statements = make(map[string]*PreparedStatement)

	psm.logger.Info("All prepared statements closed")
	return lastErr
}

// GetStats retourne les statistiques des requêtes préparées
func (psm *PreparedStatementManager) GetStats() map[string]interface{} {
	psm.mutex.RLock()
	defer psm.mutex.RUnlock()

	stats := map[string]interface{}{
		"total_statements": len(psm.statements),
		"statements":       make([]string, 0, len(psm.statements)),
	}

	for name := range psm.statements {
		stats["statements"] = append(stats["statements"].([]string), name)
	}

	return stats
}

// RefreshStatement rafraîchit une requête préparée (utile après reconnexion DB)
func (psm *PreparedStatementManager) RefreshStatement(ctx context.Context, name string) error {
	psm.mutex.Lock()
	defer psm.mutex.Unlock()

	stmt, exists := psm.statements[name]
	if !exists {
		return fmt.Errorf("statement %s not found", name)
	}

	// Fermer l'ancienne requête
	if err := stmt.Stmt.Close(); err != nil {
		psm.logger.Warn("Failed to close old statement",
			zap.String("name", name),
			zap.Error(err))
	}

	// Préparer la nouvelle requête
	newStmt, err := psm.db.PrepareContext(ctx, stmt.Query)
	if err != nil {
		return fmt.Errorf("failed to refresh statement %s: %w", name, err)
	}

	stmt.Stmt = newStmt
	psm.logger.Debug("Statement refreshed", zap.String("name", name))

	return nil
}

// RefreshAllStatements rafraîchit toutes les requêtes préparées
func (psm *PreparedStatementManager) RefreshAllStatements(ctx context.Context) error {
	psm.mutex.Lock()
	defer psm.mutex.Unlock()

	var lastErr error
	for name, stmt := range psm.statements {
		// Fermer l'ancienne requête
		if err := stmt.Stmt.Close(); err != nil {
			psm.logger.Warn("Failed to close old statement",
				zap.String("name", name),
				zap.Error(err))
		}

		// Préparer la nouvelle requête
		newStmt, err := psm.db.PrepareContext(ctx, stmt.Query)
		if err != nil {
			psm.logger.Error("Failed to refresh statement",
				zap.String("name", name),
				zap.Error(err))
			lastErr = err
			continue
		}

		stmt.Stmt = newStmt
	}

	psm.logger.Info("All statements refreshed")
	return lastErr
}
