package services

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

	"veza-backend-api/internal/models"

	"github.com/google/uuid"
	"gorm.io/gorm"

	"go.uber.org/zap"
)

// HLSService gère la récupération et le service des fichiers HLS
type HLSService struct {
	db               *gorm.DB
	outputDir        string
	logger           *zap.Logger
	transcodeService *HLSTranscodeService
	queueService     *HLSQueueService
}

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

// NewHLSServiceWithTranscode crée un nouveau service HLS avec service de transcodage
func NewHLSServiceWithTranscode(db *gorm.DB, outputDir string, transcodeService *HLSTranscodeService, logger *zap.Logger) *HLSService {
	if logger == nil {
		logger = zap.NewNop()
	}
	return &HLSService{
		db:               db,
		outputDir:        outputDir,
		logger:           logger,
		transcodeService: transcodeService,
	}
}

// SetTranscodeService définit le service de transcodage
func (s *HLSService) SetTranscodeService(transcodeService *HLSTranscodeService) {
	s.transcodeService = transcodeService
}

// SetQueueService définit le service de queue HLS
func (s *HLSService) SetQueueService(queueService *HLSQueueService) {
	s.queueService = queueService
}

// GetMasterPlaylist récupère le contenu du master playlist pour un track
func (s *HLSService) GetMasterPlaylist(ctx context.Context, trackID uuid.UUID) (string, error) {
	var hlsStream models.HLSStream
	if err := s.db.WithContext(ctx).Where("track_id = ? AND status = ?", trackID, models.HLSStatusReady).First(&hlsStream).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return "", fmt.Errorf("HLS stream not found for track %s", trackID)
		}
		return "", fmt.Errorf("failed to query HLS stream: %w", err)
	}

	// Lire le fichier master.m3u8
	// Le PlaylistURL est relatif au outputDir (ex: track_123/master.m3u8)
	masterPlaylistPath := hlsStream.PlaylistURL
	if !filepath.IsAbs(masterPlaylistPath) {
		// Si c'est un chemin relatif, il devrait déjà être relatif à outputDir
		// Vérifier si c'est déjà un chemin complet ou relatif
		if !strings.HasPrefix(masterPlaylistPath, s.outputDir) {
			masterPlaylistPath = filepath.Join(s.outputDir, masterPlaylistPath)
		}
	}

	content, err := os.ReadFile(masterPlaylistPath)
	if err != nil {
		if os.IsNotExist(err) {
			return "", fmt.Errorf("master playlist file not found: %s", masterPlaylistPath)
		}
		return "", fmt.Errorf("failed to read master playlist: %w", err)
	}

	return string(content), nil
}

// GetQualityPlaylist récupère le contenu d'une quality playlist pour un track et bitrate
func (s *HLSService) GetQualityPlaylist(ctx context.Context, trackID uuid.UUID, bitrate string) (string, error) {
	var hlsStream models.HLSStream
	if err := s.db.WithContext(ctx).Where("track_id = ? AND status = ?", trackID, models.HLSStatusReady).First(&hlsStream).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return "", fmt.Errorf("HLS stream not found for track %s", trackID)
		}
		return "", fmt.Errorf("failed to query HLS stream: %w", err)
	}

	// Construire le chemin vers la quality playlist
	trackDir := filepath.Join(s.outputDir, fmt.Sprintf("track_%s", trackID))
	qualityPlaylistPath := filepath.Join(trackDir, bitrate, "playlist.m3u8")

	content, err := os.ReadFile(qualityPlaylistPath)
	if err != nil {
		if os.IsNotExist(err) {
			return "", fmt.Errorf("quality playlist file not found: %s", qualityPlaylistPath)
		}
		return "", fmt.Errorf("failed to read quality playlist: %w", err)
	}

	return string(content), nil
}

// GetSegmentPath récupère le chemin complet d'un segment pour un track, bitrate et nom de segment
func (s *HLSService) GetSegmentPath(ctx context.Context, trackID uuid.UUID, bitrate string, segment string) (string, error) {
	var hlsStream models.HLSStream
	if err := s.db.WithContext(ctx).Where("track_id = ? AND status = ?", trackID, models.HLSStatusReady).First(&hlsStream).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return "", fmt.Errorf("HLS stream not found for track %s", trackID)
		}
		return "", fmt.Errorf("failed to query HLS stream: %w", err)
	}

	// Construire le chemin vers le segment
	trackDir := filepath.Join(s.outputDir, fmt.Sprintf("track_%s", trackID))
	segmentPath := filepath.Join(trackDir, bitrate, segment)

	// Vérifier que le fichier existe
	if _, err := os.Stat(segmentPath); os.IsNotExist(err) {
		return "", fmt.Errorf("segment file not found: %s", segmentPath)
	}

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

	absTrackDir, err := filepath.Abs(trackDir)
	if err != nil {
		return "", fmt.Errorf("failed to get absolute track dir: %w", err)
	}

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

	return absSegmentPath, nil
}

// TriggerTranscode déclenche le transcodage d'un track en HLS
func (s *HLSService) TriggerTranscode(ctx context.Context, track *models.Track) error {
	if s.transcodeService == nil {
		return fmt.Errorf("transcode service not configured")
	}

	if track == nil {
		return fmt.Errorf("track cannot be nil")
	}

	// Vérifier si un stream existe déjà pour ce track
	var existingStream models.HLSStream
	err := s.db.WithContext(ctx).Where("track_id = ?", track.ID).First(&existingStream).Error
	if err == nil {
		// Un stream existe déjà, vérifier son statut
		if existingStream.Status == models.HLSStatusReady {
			return fmt.Errorf("HLS stream already exists and is ready for track %s", track.ID)
		}
		// Si le stream est en cours de traitement ou a échoué, on peut le retranscoder
		if existingStream.Status == models.HLSStatusProcessing {
			return fmt.Errorf("HLS stream is already being processed for track %s", track.ID)
		}
		// Supprimer l'ancien stream si nécessaire
		if err := s.db.WithContext(ctx).Delete(&existingStream).Error; err != nil {
			s.logger.Warn("Failed to delete existing stream", zap.Error(err), zap.String("track_id", track.ID.String()))
		}
	}

	// Mettre à jour le statut du track si nécessaire
	if err := s.db.WithContext(ctx).Model(track).Update("status", models.TrackStatusProcessing).Error; err != nil {
		s.logger.Warn("Failed to update track status", zap.Error(err), zap.String("track_id", track.ID.String()))
	}

	// Créer un stream en statut "processing"
	hlsStream := &models.HLSStream{
		TrackID: track.ID,
		Status:  models.HLSStatusProcessing,
	}
	if err := s.db.WithContext(ctx).Create(hlsStream).Error; err != nil {
		return fmt.Errorf("failed to create HLS stream record: %w", err)
	}

	// Transcoder le track
	transcodedStream, err := s.transcodeService.TranscodeTrack(ctx, track)
	if err != nil {
		// Mettre à jour le statut en "failed"
		s.db.WithContext(ctx).Model(hlsStream).Update("status", models.HLSStatusFailed)
		return fmt.Errorf("failed to transcode track: %w", err)
	}

	// Mettre à jour le stream avec les données du transcodage
	hlsStream.PlaylistURL = transcodedStream.PlaylistURL
	hlsStream.SegmentsCount = transcodedStream.SegmentsCount
	hlsStream.Bitrates = transcodedStream.Bitrates
	hlsStream.Status = models.HLSStatusReady

	if err := s.db.WithContext(ctx).Save(hlsStream).Error; err != nil {
		return fmt.Errorf("failed to update HLS stream: %w", err)
	}

	// Mettre à jour le statut du track
	if err := s.db.WithContext(ctx).Model(track).Update("status", models.TrackStatusCompleted).Error; err != nil {
		s.logger.Warn("Failed to update track status to completed", zap.Error(err), zap.String("track_id", track.ID.String()))
	}

	s.logger.Info("HLS transcoding completed", zap.String("track_id", track.ID.String()), zap.String("stream_id", hlsStream.ID.String()))
	return nil
}

// TriggerTranscodeQueue déclenche le transcodage HLS via la queue (T0343)
// Vérifie les permissions et ajoute un job dans la queue
// MIGRATION UUID: userID migré vers uuid.UUID
func (s *HLSService) TriggerTranscodeQueue(ctx context.Context, trackID uuid.UUID, userID uuid.UUID) (uuid.UUID, error) {
	if s.queueService == nil {
		return uuid.Nil, fmt.Errorf("queue service not configured")
	}

	// Vérifier que le track existe et que l'utilisateur est propriétaire
	var track models.Track
	if err := s.db.WithContext(ctx).First(&track, "id = ?", trackID).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return uuid.Nil, fmt.Errorf("track not found")
		}
		return uuid.Nil, fmt.Errorf("failed to query track: %w", err)
	}

	// Vérifier les permissions (UUID comparison)
	if track.UserID != userID {
		return uuid.Nil, fmt.Errorf("forbidden: user does not own this track")
	}

	// Ajouter le job dans la queue avec priorité par défaut (5)
	priority := 5

	jobID, err := s.queueService.EnqueueWithID(ctx, trackID, priority)
	if err != nil {
		return uuid.Nil, fmt.Errorf("failed to enqueue transcode job: %w", err)
	}

	s.logger.Info("HLS transcode job enqueued", zap.String("job_id", jobID.String()), zap.String("track_id", trackID.String()), zap.String("user_id", userID.String()))
	return jobID, nil
}

// GetStreamStatus récupère le statut d'un stream HLS pour un track
func (s *HLSService) GetStreamStatus(ctx context.Context, trackID uuid.UUID) (map[string]interface{}, error) {
	var stream models.HLSStream
	if err := s.db.WithContext(ctx).Where("track_id = ?", trackID).First(&stream).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, fmt.Errorf("HLS stream not found for track %s", trackID)
		}
		return nil, fmt.Errorf("failed to query HLS stream: %w", err)
	}

	status := map[string]interface{}{
		"status":         stream.Status,
		"bitrates":       stream.Bitrates,
		"segments_count": stream.SegmentsCount,
		"playlist_url":   stream.PlaylistURL,
		"track_id":       stream.TrackID,
		"created_at":     stream.CreatedAt,
		"updated_at":     stream.UpdatedAt,
	}

	// Ajouter des informations supplémentaires si le stream est en cours de traitement
	if stream.Status == models.HLSStatusProcessing {
		// Vérifier s'il y a un job de transcodage en cours
		var queueJob models.HLSTranscodeQueue
		if err := s.db.WithContext(ctx).
			Where("track_id = ? AND status = ?", trackID, models.QueueStatusProcessing).
			First(&queueJob).Error; err == nil {
			status["queue_job_id"] = queueJob.ID
			status["retry_count"] = queueJob.RetryCount
			if queueJob.StartedAt != nil {
				status["started_at"] = queueJob.StartedAt
			}
		}
	}

	return status, nil
}

// GetStreamInfo récupère les informations générales d'un stream HLS pour un track
// BE-API-020: Implement HLS stream info endpoint
func (s *HLSService) GetStreamInfo(ctx context.Context, trackID uuid.UUID) (map[string]interface{}, error) {
	var stream models.HLSStream
	if err := s.db.WithContext(ctx).Where("track_id = ?", trackID).First(&stream).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, fmt.Errorf("HLS stream not found for track %s", trackID)
		}
		return nil, fmt.Errorf("failed to query HLS stream: %w", err)
	}

	info := map[string]interface{}{
		"track_id":       stream.TrackID,
		"playlist_url":   stream.PlaylistURL,
		"bitrates":       stream.Bitrates,
		"segments_count": stream.SegmentsCount,
		"created_at":     stream.CreatedAt,
		"updated_at":     stream.UpdatedAt,
	}

	return info, nil
}
