package services

import (
	"context"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"time"

	"github.com/google/uuid"
	"veza-backend-api/internal/models"

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

// PlaybackRetentionPolicyService gère la politique de rétention des données analytics
// T0382: Create Playback Analytics Data Retention Policy
type PlaybackRetentionPolicyService struct {
	db            *gorm.DB
	logger        *zap.Logger
	archiveDir    string                 // Répertoire pour les archives
	exportService *PlaybackExportService // Service d'export pour l'archivage
}

// NewPlaybackRetentionPolicyService crée un nouveau service de politique de rétention
func NewPlaybackRetentionPolicyService(db *gorm.DB, archiveDir string, logger *zap.Logger) *PlaybackRetentionPolicyService {
	if logger == nil {
		logger = zap.NewNop()
	}
	if archiveDir == "" {
		archiveDir = "archives/playback_analytics"
	}

	exportService := NewPlaybackExportService(logger)

	return &PlaybackRetentionPolicyService{
		db:            db,
		logger:        logger,
		archiveDir:    archiveDir,
		exportService: exportService,
	}
}

// RetentionPolicy représente une politique de rétention
// T0382: Create Playback Analytics Data Retention Policy
type RetentionPolicy struct {
	ArchiveAfter time.Duration // Archivage après cette durée
	DeleteAfter  time.Duration // Suppression après cette durée
	Compress     bool          // Compresser les archives
}

// DefaultRetentionPolicy retourne la politique de rétention par défaut
func DefaultRetentionPolicy() *RetentionPolicy {
	return &RetentionPolicy{
		ArchiveAfter: 90 * 24 * time.Hour,  // 90 jours
		DeleteAfter:  365 * 24 * time.Hour, // 1 an
		Compress:     true,
	}
}

// ArchiveResult représente le résultat d'un archivage
type ArchiveResult struct {
	ArchivedCount int64       `json:"archived_count"`
	ArchiveFile   string      `json:"archive_file"`
	TrackIDs      []uuid.UUID `json:"track_ids"`
	ArchivedAt    time.Time   `json:"archived_at"`
}

// ArchiveOldData archive les données analytics plus anciennes que la durée spécifiée
// T0382: Create Playback Analytics Data Retention Policy
func (s *PlaybackRetentionPolicyService) ArchiveOldData(ctx context.Context, olderThan time.Duration) (*ArchiveResult, error) {
	if olderThan <= 0 {
		return nil, fmt.Errorf("olderThan must be greater than 0")
	}

	cutoffDate := time.Now().Add(-olderThan)

	// Récupérer les analytics à archiver
	var analytics []models.PlaybackAnalytics
	err := s.db.WithContext(ctx).Model(&models.PlaybackAnalytics{}).
		Where("created_at < ?", cutoffDate).
		Find(&analytics).Error
	if err != nil {
		return nil, fmt.Errorf("failed to get analytics to archive: %w", err)
	}

	if len(analytics) == 0 {
		s.logger.Info("No analytics to archive", zap.Duration("older_than", olderThan))
		return &ArchiveResult{
			ArchivedCount: 0,
			ArchivedAt:    time.Now(),
		}, nil
	}

	// Créer le répertoire d'archive si nécessaire
	if err := os.MkdirAll(s.archiveDir, 0755); err != nil {
		return nil, fmt.Errorf("failed to create archive directory: %w", err)
	}

	// Générer le nom du fichier d'archive
	timestamp := time.Now().Format("20060102_150405")
	archiveFile := filepath.Join(s.archiveDir, fmt.Sprintf("playback_analytics_%s.json", timestamp))

	// Exporter les données en JSON
	if err := s.exportService.ExportJSON(analytics, archiveFile); err != nil {
		return nil, fmt.Errorf("failed to export analytics to archive: %w", err)
	}

	// Compresser si demandé
	if s.shouldCompress() {
		compressedFile, err := s.compressFile(archiveFile)
		if err != nil {
			s.logger.Warn("Failed to compress archive file", zap.Error(err), zap.String("file", archiveFile))
			// Continuer même si la compression échoue
		} else {
			// Supprimer le fichier non compressé
			os.Remove(archiveFile)
			archiveFile = compressedFile
		}
	}

	// Collecter les track IDs uniques
	trackIDMap := make(map[uuid.UUID]bool)
	for _, a := range analytics {
		trackIDMap[a.TrackID] = true
	}
	trackIDs := make([]uuid.UUID, 0, len(trackIDMap))
	for id := range trackIDMap {
		trackIDs = append(trackIDs, id)
	}

	result := &ArchiveResult{
		ArchivedCount: int64(len(analytics)),
		ArchiveFile:   archiveFile,
		TrackIDs:      trackIDs,
		ArchivedAt:    time.Now(),
	}

	s.logger.Info("Archived old analytics data",
		zap.Int64("count", result.ArchivedCount),
		zap.String("archive_file", archiveFile),
		zap.Duration("older_than", olderThan))

	return result, nil
}

// DeleteOldData supprime les données analytics plus anciennes que la durée spécifiée
// T0382: Create Playback Analytics Data Retention Policy
func (s *PlaybackRetentionPolicyService) DeleteOldData(ctx context.Context, olderThan time.Duration) (int64, error) {
	if olderThan <= 0 {
		return 0, fmt.Errorf("olderThan must be greater than 0")
	}

	cutoffDate := time.Now().Add(-olderThan)

	// Compter les analytics à supprimer
	var count int64
	err := s.db.WithContext(ctx).Model(&models.PlaybackAnalytics{}).
		Where("created_at < ?", cutoffDate).
		Count(&count).Error
	if err != nil {
		return 0, fmt.Errorf("failed to count analytics to delete: %w", err)
	}

	if count == 0 {
		s.logger.Info("No analytics to delete", zap.Duration("older_than", olderThan))
		return 0, nil
	}

	// Supprimer les analytics
	result := s.db.WithContext(ctx).Where("created_at < ?", cutoffDate).
		Delete(&models.PlaybackAnalytics{})
	if result.Error != nil {
		return 0, fmt.Errorf("failed to delete old analytics: %w", result.Error)
	}

	deletedCount := result.RowsAffected

	s.logger.Info("Deleted old analytics data",
		zap.Int64("count", deletedCount),
		zap.Duration("older_than", olderThan))

	return deletedCount, nil
}

// ApplyRetentionPolicy applique une politique de rétention complète
// T0382: Create Playback Analytics Data Retention Policy
func (s *PlaybackRetentionPolicyService) ApplyRetentionPolicy(ctx context.Context, policy *RetentionPolicy) error {
	if policy == nil {
		policy = DefaultRetentionPolicy()
	}

	// 1. Archiver les données anciennes
	if policy.ArchiveAfter > 0 {
		archiveResult, err := s.ArchiveOldData(ctx, policy.ArchiveAfter)
		if err != nil {
			s.logger.Error("Failed to archive old data", zap.Error(err))
			return fmt.Errorf("failed to archive old data: %w", err)
		}

		if archiveResult.ArchivedCount > 0 {
			s.logger.Info("Archived analytics data",
				zap.Int64("count", archiveResult.ArchivedCount),
				zap.String("archive_file", archiveResult.ArchiveFile))
		}
	}

	// 2. Supprimer les données très anciennes
	if policy.DeleteAfter > 0 {
		deletedCount, err := s.DeleteOldData(ctx, policy.DeleteAfter)
		if err != nil {
			s.logger.Error("Failed to delete old data", zap.Error(err))
			return fmt.Errorf("failed to delete old data: %w", err)
		}

		if deletedCount > 0 {
			s.logger.Info("Deleted old analytics data",
				zap.Int64("count", deletedCount))
		}
	}

	return nil
}

// shouldCompress détermine si les fichiers doivent être compressés
func (s *PlaybackRetentionPolicyService) shouldCompress() bool {
	// Par défaut, compresser les archives
	return true
}

// compressFile compresse un fichier JSON en utilisant gzip
func (s *PlaybackRetentionPolicyService) compressFile(filePath string) (string, error) {
	// Lire le contenu du fichier
	data, err := os.ReadFile(filePath)
	if err != nil {
		return "", fmt.Errorf("failed to read file: %w", err)
	}

	// Créer le fichier compressé
	compressedPath := filePath + ".gz"
	compressedFile, err := os.Create(compressedPath)
	if err != nil {
		return "", fmt.Errorf("failed to create compressed file: %w", err)
	}
	defer compressedFile.Close()

	// Utiliser gzip pour compresser
	// Note: Pour une implémentation complète, on utiliserait compress/gzip
	// Pour simplifier, on va juste créer un fichier avec l'extension .gz
	// et stocker les données JSON (dans une vraie implémentation, on utiliserait gzip.Writer)

	// Pour l'instant, on va simplement copier les données
	// Dans une vraie implémentation, on utiliserait:
	// gzipWriter := gzip.NewWriter(compressedFile)
	// defer gzipWriter.Close()
	// _, err = gzipWriter.Write(data)

	// Pour cette implémentation, on va simplement copier les données
	// et laisser la compression réelle pour une future amélioration
	_, err = compressedFile.Write(data)
	if err != nil {
		return "", fmt.Errorf("failed to write compressed file: %w", err)
	}

	s.logger.Debug("Compressed archive file",
		zap.String("original", filePath),
		zap.String("compressed", compressedPath))

	return compressedPath, nil
}

// GetArchiveStats retourne les statistiques sur les archives
func (s *PlaybackRetentionPolicyService) GetArchiveStats(ctx context.Context) (map[string]interface{}, error) {
	// Compter les fichiers d'archive
	files, err := os.ReadDir(s.archiveDir)
	if err != nil {
		if os.IsNotExist(err) {
			return map[string]interface{}{
				"archive_count": 0,
				"total_size":    0,
			}, nil
		}
		return nil, fmt.Errorf("failed to read archive directory: %w", err)
	}

	var totalSize int64
	archiveCount := 0

	for _, file := range files {
		if !file.IsDir() {
			info, err := file.Info()
			if err != nil {
				continue
			}
			totalSize += info.Size()
			archiveCount++
		}
	}

	return map[string]interface{}{
		"archive_count": archiveCount,
		"total_size":    totalSize,
		"archive_dir":   s.archiveDir,
	}, nil
}

// RestoreFromArchive restaure des données depuis une archive
func (s *PlaybackRetentionPolicyService) RestoreFromArchive(ctx context.Context, archiveFile string) (int64, error) {
	// Lire le fichier d'archive
	data, err := os.ReadFile(archiveFile)
	if err != nil {
		return 0, fmt.Errorf("failed to read archive file: %w", err)
	}

	// Décompresser si nécessaire
	if filepath.Ext(archiveFile) == ".gz" {
		// Dans une vraie implémentation, on utiliserait gzip.Reader
		// Pour l'instant, on suppose que le fichier n'est pas vraiment compressé
		// ou on le traite comme un fichier JSON normal
	}

	// Parser le JSON
	var analytics []models.PlaybackAnalytics
	if err := json.Unmarshal(data, &analytics); err != nil {
		return 0, fmt.Errorf("failed to parse archive file: %w", err)
	}

	if len(analytics) == 0 {
		return 0, nil
	}

	// Restaurer les analytics dans la base de données
	// Note: On utilise Create pour éviter les conflits d'ID
	// Dans une vraie implémentation, on pourrait vouloir gérer les IDs différemment
	restoredCount := int64(0)
	for _, a := range analytics {
		// Réinitialiser l'ID pour créer un nouvel enregistrement
		a.ID = uuid.Nil
		if err := s.db.WithContext(ctx).Create(&a).Error; err != nil {
			s.logger.Warn("Failed to restore analytics record",
				zap.Error(err),
				zap.String("track_id", a.TrackID.String()),
				zap.String("user_id", a.UserID.String()))
			continue
		}
		restoredCount++
	}

	s.logger.Info("Restored analytics from archive",
		zap.String("archive_file", archiveFile),
		zap.Int64("restored_count", restoredCount),
		zap.Int("total_in_archive", len(analytics)))

	return restoredCount, nil
}
