//! Service de cache Redis pour optimiser les performances
//!
//! Ce service implémente une stratégie cache-aside avec invalidation automatique
//! pour améliorer les performances des requêtes fréquentes.

package services

import (
	"context"
	"encoding/json"
	"fmt"
	"time"

	"github.com/google/uuid"

	"github.com/redis/go-redis/v9"
	"go.uber.org/zap"
)

// CacheService gère le cache Redis avec différentes stratégies
type CacheService struct {
	client *redis.Client
	logger *zap.Logger
}

// CacheConfig contient la configuration du cache
type CacheConfig struct {
	DefaultTTL  time.Duration
	UserTTL     time.Duration
	TrackTTL    time.Duration
	PlaylistTTL time.Duration
	RoomTTL     time.Duration
}

// DefaultCacheConfig retourne la configuration par défaut du cache
func DefaultCacheConfig() *CacheConfig {
	return &CacheConfig{
		DefaultTTL:  5 * time.Minute,
		UserTTL:     5 * time.Minute,
		TrackTTL:    30 * time.Minute,
		PlaylistTTL: 15 * time.Minute,
		RoomTTL:     1 * time.Minute,
	}
}

// NewCacheService crée un nouveau service de cache
func NewCacheService(client *redis.Client, logger *zap.Logger) *CacheService {
	return &CacheService{
		client: client,
		logger: logger,
	}
}

// Set stocke une valeur dans le cache avec TTL
func (c *CacheService) Set(ctx context.Context, key string, value interface{}, ttl time.Duration) error {
	data, err := json.Marshal(value)
	if err != nil {
		return fmt.Errorf("failed to marshal value: %w", err)
	}

	err = c.client.Set(ctx, key, data, ttl).Err()
	if err != nil {
		c.logger.Error("Failed to set cache value",
			zap.String("key", key),
			zap.Error(err))
		return err
	}

	c.logger.Debug("Cache value set",
		zap.String("key", key),
		zap.Duration("ttl", ttl))

	return nil
}

// Get récupère une valeur du cache
func (c *CacheService) Get(ctx context.Context, key string, dest interface{}) error {
	data, err := c.client.Get(ctx, key).Result()
	if err != nil {
		if err == redis.Nil {
			return ErrCacheMiss
		}
		c.logger.Error("Failed to get cache value",
			zap.String("key", key),
			zap.Error(err))
		return err
	}

	err = json.Unmarshal([]byte(data), dest)
	if err != nil {
		c.logger.Error("Failed to unmarshal cache value",
			zap.String("key", key),
			zap.Error(err))
		return err
	}

	c.logger.Debug("Cache value retrieved", zap.String("key", key))
	return nil
}

// Delete supprime une valeur du cache
func (c *CacheService) Delete(ctx context.Context, key string) error {
	err := c.client.Del(ctx, key).Err()
	if err != nil {
		c.logger.Error("Failed to delete cache value",
			zap.String("key", key),
			zap.Error(err))
		return err
	}

	c.logger.Debug("Cache value deleted", zap.String("key", key))
	return nil
}

// DeletePattern supprime toutes les clés correspondant à un pattern
func (c *CacheService) DeletePattern(ctx context.Context, pattern string) error {
	keys, err := c.client.Keys(ctx, pattern).Result()
	if err != nil {
		c.logger.Error("Failed to get keys by pattern",
			zap.String("pattern", pattern),
			zap.Error(err))
		return err
	}

	if len(keys) > 0 {
		err = c.client.Del(ctx, keys...).Err()
		if err != nil {
			c.logger.Error("Failed to delete keys by pattern",
				zap.String("pattern", pattern),
				zap.Error(err))
			return err
		}

		c.logger.Debug("Cache keys deleted by pattern",
			zap.String("pattern", pattern),
			zap.Int("count", len(keys)))
	}

	return nil
}

// Exists vérifie si une clé existe dans le cache
func (c *CacheService) Exists(ctx context.Context, key string) (bool, error) {
	count, err := c.client.Exists(ctx, key).Result()
	if err != nil {
		c.logger.Error("Failed to check cache key existence",
			zap.String("key", key),
			zap.Error(err))
		return false, err
	}

	return count > 0, nil
}

// SetUser met en cache les données d'un utilisateur
// BE-SVC-001: Implement caching layer for frequently accessed data
func (c *CacheService) SetUser(ctx context.Context, userID uuid.UUID, user interface{}, config *CacheConfig) error {
	key := fmt.Sprintf("user:%s", userID.String())
	return c.Set(ctx, key, user, config.UserTTL)
}

// GetUser récupère les données d'un utilisateur depuis le cache
func (c *CacheService) GetUser(ctx context.Context, userID uuid.UUID, dest interface{}) error {
	key := fmt.Sprintf("user:%s", userID.String())
	return c.Get(ctx, key, dest)
}

// DeleteUser supprime les données d'un utilisateur du cache
func (c *CacheService) DeleteUser(ctx context.Context, userID uuid.UUID) error {
	key := fmt.Sprintf("user:%s", userID.String())
	return c.Delete(ctx, key)
}

// SetTrack met en cache les métadonnées d'un track
// BE-SVC-001: Support UUID for tracks
func (c *CacheService) SetTrack(ctx context.Context, trackID uuid.UUID, track interface{}, config *CacheConfig) error {
	key := fmt.Sprintf("track:%s", trackID.String())
	return c.Set(ctx, key, track, config.TrackTTL)
}

// GetTrack récupère les métadonnées d'un track depuis le cache
func (c *CacheService) GetTrack(ctx context.Context, trackID uuid.UUID, dest interface{}) error {
	key := fmt.Sprintf("track:%s", trackID.String())
	return c.Get(ctx, key, dest)
}

// DeleteTrack supprime les métadonnées d'un track du cache
func (c *CacheService) DeleteTrack(ctx context.Context, trackID uuid.UUID) error {
	key := fmt.Sprintf("track:%s", trackID.String())
	return c.Delete(ctx, key)
}

// SetPlaylist met en cache les données d'une playlist
// BE-SVC-001: Add playlist caching
func (c *CacheService) SetPlaylist(ctx context.Context, playlistID uuid.UUID, playlist interface{}, config *CacheConfig) error {
	key := fmt.Sprintf("playlist:%s", playlistID.String())
	return c.Set(ctx, key, playlist, config.PlaylistTTL)
}

// GetPlaylist récupère les données d'une playlist depuis le cache
func (c *CacheService) GetPlaylist(ctx context.Context, playlistID uuid.UUID, dest interface{}) error {
	key := fmt.Sprintf("playlist:%s", playlistID.String())
	return c.Get(ctx, key, dest)
}

// DeletePlaylist supprime les données d'une playlist du cache
func (c *CacheService) DeletePlaylist(ctx context.Context, playlistID uuid.UUID) error {
	key := fmt.Sprintf("playlist:%s", playlistID.String())
	return c.Delete(ctx, key)
}

// SetRoom met en cache les données d'une room/conversation
func (c *CacheService) SetRoom(ctx context.Context, roomID int64, room interface{}, config *CacheConfig) error {
	key := fmt.Sprintf("room:%d", roomID)
	return c.Set(ctx, key, room, config.RoomTTL)
}

// GetRoom récupère les données d'une room depuis le cache
func (c *CacheService) GetRoom(ctx context.Context, roomID int64, dest interface{}) error {
	key := fmt.Sprintf("room:%d", roomID)
	return c.Get(ctx, key, dest)
}

// DeleteRoom supprime les données d'une room du cache
func (c *CacheService) DeleteRoom(ctx context.Context, roomID int64) error {
	key := fmt.Sprintf("room:%d", roomID)
	return c.Delete(ctx, key)
}

// SetMessages met en cache une liste de messages
func (c *CacheService) SetMessages(ctx context.Context, roomID int64, page int, messages interface{}, config *CacheConfig) error {
	key := fmt.Sprintf("messages:%d:page:%d", roomID, page)
	return c.Set(ctx, key, messages, config.RoomTTL)
}

// GetMessages récupère une liste de messages depuis le cache
func (c *CacheService) GetMessages(ctx context.Context, roomID int64, page int, dest interface{}) error {
	key := fmt.Sprintf("messages:%d:page:%d", roomID, page)
	return c.Get(ctx, key, dest)
}

// DeleteRoomMessages supprime tous les messages d'une room du cache
func (c *CacheService) DeleteRoomMessages(ctx context.Context, roomID int64) error {
	pattern := fmt.Sprintf("messages:%d:*", roomID)
	return c.DeletePattern(ctx, pattern)
}

// SetUserTracks met en cache la liste des tracks d'un utilisateur
func (c *CacheService) SetUserTracks(ctx context.Context, userID uuid.UUID, page int, tracks interface{}, config *CacheConfig) error {
	key := fmt.Sprintf("user_tracks:%s:page:%d", userID.String(), page)
	return c.Set(ctx, key, tracks, config.TrackTTL)
}

// GetUserTracks récupère la liste des tracks d'un utilisateur depuis le cache
func (c *CacheService) GetUserTracks(ctx context.Context, userID uuid.UUID, page int, dest interface{}) error {
	key := fmt.Sprintf("user_tracks:%s:page:%d", userID.String(), page)
	return c.Get(ctx, key, dest)
}

// DeleteUserTracks supprime tous les tracks d'un utilisateur du cache
func (c *CacheService) DeleteUserTracks(ctx context.Context, userID uuid.UUID) error {
	pattern := fmt.Sprintf("user_tracks:%s:*", userID.String())
	return c.DeletePattern(ctx, pattern)
}

// SetSearchResults met en cache les résultats de recherche
func (c *CacheService) SetSearchResults(ctx context.Context, query string, results interface{}, config *CacheConfig) error {
	key := fmt.Sprintf("search:%s", query)
	return c.Set(ctx, key, results, config.DefaultTTL)
}

// GetSearchResults récupère les résultats de recherche depuis le cache
func (c *CacheService) GetSearchResults(ctx context.Context, query string, dest interface{}) error {
	key := fmt.Sprintf("search:%s", query)
	return c.Get(ctx, key, dest)
}

// InvalidateUserCache invalide tout le cache lié à un utilisateur
func (c *CacheService) InvalidateUserCache(ctx context.Context, userID uuid.UUID) error {
	patterns := []string{
		fmt.Sprintf("user:%s", userID.String()),
		fmt.Sprintf("user_tracks:%s:*", userID.String()),
		fmt.Sprintf("user_sessions:%s:*", userID.String()),
	}

	for _, pattern := range patterns {
		if err := c.DeletePattern(ctx, pattern); err != nil {
			c.logger.Error("Failed to invalidate user cache pattern",
				zap.String("pattern", pattern),
				zap.Error(err))
		}
	}

	c.logger.Info("User cache invalidated", zap.String("user_id", userID.String()))
	return nil
}

// InvalidateTrackCache invalide tout le cache lié à un track
// BE-SVC-001: Support UUID for tracks
func (c *CacheService) InvalidateTrackCache(ctx context.Context, trackID uuid.UUID) error {
	patterns := []string{
		fmt.Sprintf("track:%s", trackID.String()),
		"search:*", // Invalider les recherches car le track peut apparaître dans les résultats
	}

	for _, pattern := range patterns {
		if err := c.DeletePattern(ctx, pattern); err != nil {
			c.logger.Error("Failed to invalidate track cache pattern",
				zap.String("pattern", pattern),
				zap.Error(err))
		}
	}

	c.logger.Info("Track cache invalidated", zap.String("track_id", trackID.String()))
	return nil
}

// InvalidatePlaylistCache invalide tout le cache lié à une playlist
// BE-SVC-001: Add playlist cache invalidation
func (c *CacheService) InvalidatePlaylistCache(ctx context.Context, playlistID uuid.UUID) error {
	patterns := []string{
		fmt.Sprintf("playlist:%s", playlistID.String()),
		"search:*", // Invalider les recherches car la playlist peut apparaître dans les résultats
	}

	for _, pattern := range patterns {
		if err := c.DeletePattern(ctx, pattern); err != nil {
			c.logger.Error("Failed to invalidate playlist cache pattern",
				zap.String("pattern", pattern),
				zap.Error(err))
		}
	}

	c.logger.Info("Playlist cache invalidated", zap.String("playlist_id", playlistID.String()))
	return nil
}

// InvalidateRoomCache invalide tout le cache lié à une room
func (c *CacheService) InvalidateRoomCache(ctx context.Context, roomID int64) error {
	patterns := []string{
		fmt.Sprintf("room:%d", roomID),
		fmt.Sprintf("messages:%d:*", roomID),
	}

	for _, pattern := range patterns {
		if err := c.DeletePattern(ctx, pattern); err != nil {
			c.logger.Error("Failed to invalidate room cache pattern",
				zap.String("pattern", pattern),
				zap.Error(err))
		}
	}

	c.logger.Info("Room cache invalidated", zap.Int64("room_id", roomID))
	return nil
}

// GetStats retourne les statistiques du cache
func (c *CacheService) GetStats(ctx context.Context) (*CacheStats, error) {
	info, err := c.client.Info(ctx, "memory", "stats").Result()
	if err != nil {
		return nil, err
	}

	// Parser les informations Redis pour extraire les métriques
	stats := &CacheStats{
		Info: info,
	}

	return stats, nil
}

// CacheStats contient les statistiques du cache
type CacheStats struct {
	Info string `json:"info"`
}

// ErrCacheMiss est retourné quand une clé n'existe pas dans le cache
var ErrCacheMiss = fmt.Errorf("cache miss")

// Close ferme la connexion Redis
func (c *CacheService) Close() error {
	return c.client.Close()
}
