package services

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

	"github.com/google/uuid"

	"veza-backend-api/internal/models"
	"veza-backend-api/internal/repositories"

	"go.uber.org/zap"
	"gorm.io/gorm"
)

// PlaylistVersionService gère les versions de playlists
// T0509: Create Playlist Version History
type PlaylistVersionService struct {
	versionRepo       repositories.PlaylistVersionRepository
	playlistRepo      repositories.PlaylistRepository
	playlistTrackRepo repositories.PlaylistTrackRepository
	logger            *zap.Logger
}

// NewPlaylistVersionService crée un nouveau service de versions de playlists
func NewPlaylistVersionService(
	versionRepo repositories.PlaylistVersionRepository,
	playlistRepo repositories.PlaylistRepository,
	playlistTrackRepo repositories.PlaylistTrackRepository,
	logger *zap.Logger,
) *PlaylistVersionService {
	if logger == nil {
		logger = zap.NewNop()
	}
	return &PlaylistVersionService{
		versionRepo:       versionRepo,
		playlistRepo:      playlistRepo,
		playlistTrackRepo: playlistTrackRepo,
		logger:            logger,
	}
}

// SaveVersion sauvegarde une version de la playlist
// T0509: Create Playlist Version History
// MIGRATION UUID: userID en uuid.UUID, playlistID en uuid.UUID
func (s *PlaylistVersionService) SaveVersion(ctx context.Context, playlistID uuid.UUID, userID uuid.UUID, action models.PlaylistVersionAction) (*models.PlaylistVersion, error) {
	// Récupérer la playlist avec ses tracks
	playlist, err := s.playlistRepo.GetByIDWithTracks(ctx, playlistID)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, errors.New("playlist not found")
		}
		return nil, fmt.Errorf("failed to get playlist: %w", err)
	}

	// Obtenir le prochain numéro de version
	versionNumber, err := s.versionRepo.GetNextVersionNumber(ctx, playlistID)
	if err != nil {
		return nil, fmt.Errorf("failed to get next version number: %w", err)
	}

	// Créer un snapshot des tracks
	tracksSnapshot, err := s.createTracksSnapshot(ctx, playlistID)
	if err != nil {
		s.logger.Warn("Failed to create tracks snapshot", zap.Error(err))
		// Continuer même si le snapshot échoue
		tracksSnapshot = "[]"
	}

	// Créer la version
	// FIXME: models.PlaylistVersion ID types need check. Assuming repo handles UUID if struct updated.
	version := &models.PlaylistVersion{
		PlaylistID:     playlistID, // Assuming struct updated to UUID
		UserID:         userID,
		Version:        versionNumber,
		Action:         action,
		Title:          playlist.Title,
		Description:    playlist.Description,
		IsPublic:       playlist.IsPublic,
		CoverURL:       playlist.CoverURL,
		TracksSnapshot: tracksSnapshot,
	}

	if err := s.versionRepo.Create(ctx, version); err != nil {
		return nil, fmt.Errorf("failed to create version: %w", err)
	}

	s.logger.Info("Playlist version saved",
		zap.String("playlist_id", playlistID.String()),
		zap.String("user_id", userID.String()),
		zap.Int("version", versionNumber),
		zap.String("action", string(action)),
	)

	return version, nil
}

// createTracksSnapshot crée un snapshot JSON des tracks de la playlist
func (s *PlaylistVersionService) createTracksSnapshot(ctx context.Context, playlistID uuid.UUID) (string, error) {
	// Récupérer les tracks de la playlist
	playlist, err := s.playlistRepo.GetByIDWithTracks(ctx, playlistID)
	if err != nil {
		return "", err
	}

	// Créer un snapshot simple avec les IDs et positions
	type TrackSnapshot struct {
		TrackID  uuid.UUID `json:"track_id"`
		Position int       `json:"position"`
	}

	snapshots := make([]TrackSnapshot, 0, len(playlist.Tracks))
	for _, track := range playlist.Tracks {
		snapshots = append(snapshots, TrackSnapshot{
			TrackID:  track.TrackID,
			Position: track.Position,
		})
	}

	// Sérialiser en JSON
	data, err := json.Marshal(snapshots)
	if err != nil {
		return "", fmt.Errorf("failed to marshal tracks snapshot: %w", err)
	}

	return string(data), nil
}

// GetVersions récupère l'historique des versions d'une playlist
// T0509: Create Playlist Version History
func (s *PlaylistVersionService) GetVersions(ctx context.Context, playlistID uuid.UUID, limit, offset int) ([]*models.PlaylistVersion, int64, error) {
	return s.versionRepo.GetByPlaylistID(ctx, playlistID, limit, offset)
}

// GetVersion récupère une version spécifique
// T0509: Create Playlist Version History
func (s *PlaylistVersionService) GetVersion(ctx context.Context, playlistID uuid.UUID, version int) (*models.PlaylistVersion, error) {
	return s.versionRepo.GetByVersion(ctx, playlistID, version)
}

// RestoreVersion restaure une playlist à une version spécifique
// T0509: Create Playlist Version History
// MIGRATION UUID: userID en uuid.UUID, playlistID en uuid.UUID
func (s *PlaylistVersionService) RestoreVersion(ctx context.Context, playlistID uuid.UUID, userID uuid.UUID, version int) (*models.PlaylistVersion, error) {
	// Récupérer la version à restaurer
	versionToRestore, err := s.versionRepo.GetByVersion(ctx, playlistID, version)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, errors.New("version not found")
		}
		return nil, fmt.Errorf("failed to get version: %w", err)
	}

	// Récupérer la playlist actuelle
	playlist, err := s.playlistRepo.GetByID(ctx, playlistID)
	if err != nil {
		return nil, fmt.Errorf("failed to get playlist: %w", err)
	}

	// Restaurer les propriétés de la playlist
	playlist.Title = versionToRestore.Title
	playlist.Description = versionToRestore.Description
	playlist.IsPublic = versionToRestore.IsPublic
	playlist.CoverURL = versionToRestore.CoverURL

	if err := s.playlistRepo.Update(ctx, playlist); err != nil {
		return nil, fmt.Errorf("failed to update playlist: %w", err)
	}

	// Restaurer les tracks si le snapshot existe
	if versionToRestore.TracksSnapshot != "" {
		if err := s.restoreTracksFromSnapshot(ctx, playlistID, versionToRestore.TracksSnapshot); err != nil {
			s.logger.Warn("Failed to restore tracks from snapshot", zap.Error(err))
			// Ne pas échouer la restauration si les tracks ne peuvent pas être restaurés
		}
	}

	// Créer une nouvelle version pour la restauration
	restoredVersion, err := s.SaveVersion(ctx, playlistID, userID, models.PlaylistVersionActionRestored)
	if err != nil {
		s.logger.Warn("Failed to save restored version", zap.Error(err))
		// Retourner quand même la version restaurée
		return versionToRestore, nil
	}

	s.logger.Info("Playlist version restored",
		zap.String("playlist_id", playlistID.String()),
		zap.String("user_id", userID.String()),
		zap.Int("restored_version", version),
		zap.Int("new_version", restoredVersion.Version),
	)

	return restoredVersion, nil
}

// restoreTracksFromSnapshot restaure les tracks depuis un snapshot
func (s *PlaylistVersionService) restoreTracksFromSnapshot(_ context.Context, playlistID uuid.UUID, snapshot string) error {
	type TrackSnapshot struct {
		TrackID  uuid.UUID `json:"track_id"`
		Position int       `json:"position"`
	}

	var snapshots []TrackSnapshot
	if err := json.Unmarshal([]byte(snapshot), &snapshots); err != nil {
		return fmt.Errorf("failed to unmarshal tracks snapshot: %w", err)
	}

	// Supprimer tous les tracks actuels
	// Note: Cette opération peut être coûteuse, mais nécessaire pour une restauration complète
	// Dans une implémentation optimisée, on pourrait comparer et ne modifier que ce qui a changé

	// Pour l'instant, on ne restaure pas automatiquement les tracks car cela nécessite
	// de supprimer tous les tracks existants et de les recréer, ce qui peut être risqué
	// Cette fonctionnalité peut être ajoutée plus tard si nécessaire

	s.logger.Info("Tracks snapshot restoration skipped (not implemented)",
		zap.String("playlist_id", playlistID.String()),
		zap.Int("tracks_count", len(snapshots)),
	)

	return nil
}
