package services

import (
	"context"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"veza-backend-api/internal/models"

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

// HLSCleanupService gère le nettoyage des segments HLS obsolètes
type HLSCleanupService struct {
	db        *gorm.DB
	outputDir string
	logger    *zap.Logger
}

// NewHLSCleanupService crée un nouveau service de cleanup HLS
func NewHLSCleanupService(db *gorm.DB, outputDir string, logger *zap.Logger) *HLSCleanupService {
	if logger == nil {
		logger = zap.NewNop()
	}
	return &HLSCleanupService{
		db:        db,
		outputDir: outputDir,
		logger:    logger,
	}
}

// CleanupDeletedTracks nettoie les segments HLS des tracks supprimés
// MIGRATION UUID: Completée. TrackID et StreamID en UUID.
func (s *HLSCleanupService) CleanupDeletedTracks(ctx context.Context) (int, error) {
	var streams []models.HLSStream
	if err := s.db.WithContext(ctx).Find(&streams).Error; err != nil {
		return 0, fmt.Errorf("failed to fetch streams: %w", err)
	}

	cleanedCount := 0
	for _, stream := range streams {
		var track models.Track
		if err := s.db.WithContext(ctx).First(&track, "id = ?", stream.TrackID).Error; err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				// Track deleted, cleanup segments
				s.logger.Info("Cleaning up segments for deleted track",
					zap.String("stream_id", stream.ID.String()),
					zap.String("track_id", stream.TrackID.String()))

				if err := s.cleanupStreamFiles(stream); err != nil {
					s.logger.Error("Failed to cleanup stream files",
						zap.String("stream_id", stream.ID.String()),
						zap.Error(err))
					// Continue avec les autres streams même en cas d'erreur
				}

				if err := s.db.WithContext(ctx).Delete(&stream).Error; err != nil {
					s.logger.Error("Failed to delete stream record",
						zap.String("stream_id", stream.ID.String()),
						zap.Error(err))
					// Continue avec les autres streams
				} else {
					cleanedCount++
				}
			} else {
				s.logger.Error("Failed to check track existence",
					zap.String("stream_id", stream.ID.String()),
					zap.String("track_id", stream.TrackID.String()),
					zap.Error(err))
			}
		}
	}

	s.logger.Info("Cleanup deleted tracks completed",
		zap.Int("cleaned_count", cleanedCount))
	return cleanedCount, nil
}

// CleanupOrphanedSegments nettoie les segments HLS qui n'ont pas de stream associé dans la base de données
func (s *HLSCleanupService) CleanupOrphanedSegments(ctx context.Context) (int, error) {
	// Récupérer tous les streams valides
	var streams []models.HLSStream
	if err := s.db.WithContext(ctx).Find(&streams).Error; err != nil {
		return 0, fmt.Errorf("failed to fetch streams: %w", err)
	}

	// Créer un map des répertoires de streams valides
	validDirs := make(map[string]bool)
	for _, stream := range streams {
		// Construire le chemin du répertoire du stream
		trackDir := filepath.Join(s.outputDir, fmt.Sprintf("track_%s", stream.TrackID))
		validDirs[trackDir] = true
	}

	// Parcourir le répertoire de sortie HLS
	cleanedCount := 0
	err := filepath.Walk(s.outputDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			// Ignorer les erreurs de lecture de répertoire
			return nil
		}

		// Vérifier si c'est un répertoire de track (format: track_XXX)
		if !info.IsDir() {
			return nil
		}

		// Obtenir le répertoire parent pour vérifier si c'est un track_XXX
		dir := path
		base := filepath.Base(dir)
		if !strings.HasPrefix(base, "track_") {
			return nil
		}

		// Vérifier si ce répertoire est dans la liste des répertoires valides
		if !validDirs[dir] {
			s.logger.Info("Found orphaned segment directory",
				zap.String("path", dir))

			// Supprimer le répertoire orphelin
			if err := os.RemoveAll(dir); err != nil {
				s.logger.Error("Failed to remove orphaned directory",
					zap.String("path", dir),
					zap.Error(err))
				return nil // Continue avec les autres répertoires
			}

			cleanedCount++
		}

		return nil
	})

	if err != nil {
		return cleanedCount, fmt.Errorf("failed to walk output directory: %w", err)
	}

	s.logger.Info("Cleanup orphaned segments completed",
		zap.Int("cleaned_count", cleanedCount))
	return cleanedCount, nil
}

// cleanupStreamFiles supprime les fichiers d'un stream
func (s *HLSCleanupService) cleanupStreamFiles(stream models.HLSStream) error {
	// Construire le chemin du répertoire du track
	trackDir := filepath.Join(s.outputDir, fmt.Sprintf("track_%s", stream.TrackID))

	// Vérifier que le chemin est sécurisé (pas de directory traversal)
	absTrackDir, err := filepath.Abs(trackDir)
	if err != nil {
		return fmt.Errorf("failed to get absolute path: %w", err)
	}

	absOutputDir, err := filepath.Abs(s.outputDir)
	if err != nil {
		return fmt.Errorf("failed to get absolute output dir: %w", err)
	}

	// Vérifier que le répertoire est bien dans outputDir
	if !strings.HasPrefix(absTrackDir, absOutputDir) {
		return fmt.Errorf("invalid track directory path: %s", trackDir)
	}

	// Supprimer le répertoire et tous ses contenus
	if err := os.RemoveAll(trackDir); err != nil {
		return fmt.Errorf("failed to remove track directory: %w", err)
	}

	s.logger.Debug("Cleaned up stream files",
		zap.String("track_id", stream.TrackID.String()),
		zap.String("track_dir", trackDir))

	return nil
}

// CleanupAll exécute tous les nettoyages
func (s *HLSCleanupService) CleanupAll(ctx context.Context) error {
	s.logger.Info("Starting HLS cleanup")

	// Nettoyer les tracks supprimés
	deletedCount, err := s.CleanupDeletedTracks(ctx)
	if err != nil {
		s.logger.Error("Failed to cleanup deleted tracks", zap.Error(err))
		return fmt.Errorf("failed to cleanup deleted tracks: %w", err)
	}

	// Nettoyer les segments orphelins
	orphanedCount, err := s.CleanupOrphanedSegments(ctx)
	if err != nil {
		s.logger.Error("Failed to cleanup orphaned segments", zap.Error(err))
		return fmt.Errorf("failed to cleanup orphaned segments: %w", err)
	}

	s.logger.Info("HLS cleanup completed",
		zap.Int("deleted_tracks_cleaned", deletedCount),
		zap.Int("orphaned_segments_cleaned", orphanedCount))

	return nil
}
