package services

import (
	"context"
	"fmt"

	"veza-backend-api/internal/models"

	"github.com/google/uuid"

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

// BitrateAdaptationService gère l'adaptation de bitrate pour le streaming
// T0348: Create Bitrate Adaptation Service
type BitrateAdaptationService struct {
	db               *gorm.DB
	bandwidthService *BandwidthDetectionService
	logger           *zap.Logger
}

// NewBitrateAdaptationService crée un nouveau service d'adaptation de bitrate
func NewBitrateAdaptationService(db *gorm.DB, bandwidthService *BandwidthDetectionService, logger *zap.Logger) *BitrateAdaptationService {
	if logger == nil {
		logger = zap.NewNop()
	}
	return &BitrateAdaptationService{
		db:               db,
		bandwidthService: bandwidthService,
		logger:           logger,
	}
}

// AdaptBitrate adapte le bitrate en fonction de la bande passante et du niveau de buffer
// trackID: ID de la piste audio
// userID: ID de l'utilisateur
// currentBitrate: bitrate actuel en kbps
// bandwidth: bande passante disponible en bps
// bufferLevel: niveau de buffer (0.0 à 1.0)
// Retourne le nouveau bitrate recommandé en kbps
// MIGRATION UUID: userID est maintenant int64
// MIGRATION UUID: userID migré vers uuid.UUID
func (s *BitrateAdaptationService) AdaptBitrate(ctx context.Context, trackID uuid.UUID, userID uuid.UUID, currentBitrate int, bandwidth int64, bufferLevel float64) (int, error) {
	// Valider les paramètres
	// Valider les paramètres
	if trackID == uuid.Nil {
		return currentBitrate, fmt.Errorf("0: %w", ErrInvalidTrackID)
	}
	if userID == uuid.Nil {
		return currentBitrate, fmt.Errorf("nil UUID: %w", ErrInvalidUserID)
	}
	if currentBitrate <= 0 {
		return currentBitrate, fmt.Errorf("invalid current bitrate: %d", currentBitrate)
	}
	if bufferLevel < 0 || bufferLevel > 1 {
		return currentBitrate, fmt.Errorf("%f (must be between 0.0 and 1.0): %w", bufferLevel, ErrInvalidBufferLevel)
	}

	// Obtenir la recommandation de bitrate basée sur la bande passante
	recommendedBitrate := s.bandwidthService.RecommendBitrate(bandwidth)

	// Ajuster en fonction du niveau de buffer
	// Si le buffer est faible (< 20%), ne pas augmenter le bitrate
	if bufferLevel < 0.2 && recommendedBitrate > currentBitrate {
		recommendedBitrate = currentBitrate
		s.logger.Debug("Bitrate increase prevented due to low buffer",
			zap.String("track_id", trackID.String()),
			zap.String("user_id", userID.String()),
			zap.Int("current_bitrate", currentBitrate),
			zap.Int("recommended_bitrate", recommendedBitrate),
			zap.Float64("buffer_level", bufferLevel))
	}

	// Si le buffer est très faible (<= 10%), réduire le bitrate
	if bufferLevel <= 0.1 && recommendedBitrate >= currentBitrate {
		// Réduire d'un niveau
		// Réduire d'un niveau
		switch currentBitrate {
		case 320:
			recommendedBitrate = 192
		case 192:
			recommendedBitrate = 128
		default:
			recommendedBitrate = 128
		}
		s.logger.Debug("Bitrate reduced due to very low buffer",
			zap.String("track_id", trackID.String()),
			zap.String("user_id", userID.String()),
			zap.Int("current_bitrate", currentBitrate),
			zap.Int("new_bitrate", recommendedBitrate),
			zap.Float64("buffer_level", bufferLevel))
	}

	// Si le bitrate a changé, logger l'adaptation
	if recommendedBitrate != currentBitrate {
		reason := s.determineReason(currentBitrate, recommendedBitrate, bufferLevel)

		log := &models.BitrateAdaptationLog{
			TrackID:          trackID,
			UserID:           userID,
			OldBitrate:       currentBitrate,
			NewBitrate:       recommendedBitrate,
			Reason:           reason,
			NetworkBandwidth: intPtr(int(bandwidth)),
		}

		if err := s.db.WithContext(ctx).Create(log).Error; err != nil {
			s.logger.Error("Failed to create bitrate adaptation log",
				zap.Error(err),
				zap.String("track_id", trackID.String()),
				zap.String("user_id", userID.String()))
			// Ne pas retourner l'erreur, l'adaptation peut continuer même si le log échoue
		} else {
			s.logger.Info("Bitrate adaptation logged",
				zap.String("track_id", trackID.String()),
				zap.String("user_id", userID.String()),
				zap.Int("old_bitrate", currentBitrate),
				zap.Int("new_bitrate", recommendedBitrate),
				zap.String("reason", string(reason)))
		}
	}

	return recommendedBitrate, nil
}

// determineReason détermine la raison de l'adaptation de bitrate
func (s *BitrateAdaptationService) determineReason(old, new int, bufferLevel float64) models.BitrateAdaptationReason {
	// Si le buffer est faible, c'est la raison principale
	if bufferLevel < 0.2 {
		return models.BitrateReasonBufferLow
	}

	// Sinon, déterminer selon si on augmente ou diminue
	if new > old {
		return models.BitrateReasonNetworkFast
	} else if new < old {
		return models.BitrateReasonNetworkSlow
	}

	// Par défaut (ne devrait pas arriver)
	return models.BitrateReasonNetworkSlow
}

// BitrateAnalytics représente les statistiques d'adaptation de bitrate
// T0354: Create Bitrate Adaptation Analytics Endpoint
type BitrateAnalytics struct {
	TotalAdaptations    int64                 `json:"total_adaptations"`
	Reasons             map[string]int64      `json:"reasons"`
	AdaptationsOverTime []AdaptationTimePoint `json:"adaptations_over_time"`
	AverageBandwidth    *float64              `json:"average_bandwidth,omitempty"`
}

// AdaptationTimePoint représente un point dans le temps pour l'évolution des adaptations
type AdaptationTimePoint struct {
	Date  string `json:"date"`
	Count int64  `json:"count"`
}

// GetAnalytics récupère les statistiques d'adaptation de bitrate pour un track
// T0354: Create Bitrate Adaptation Analytics Endpoint
func (s *BitrateAdaptationService) GetAnalytics(ctx context.Context, trackID uuid.UUID) (*BitrateAnalytics, error) {
	if trackID == uuid.Nil {
		return nil, fmt.Errorf("0: %w", ErrInvalidTrackID)
	}

	analytics := &BitrateAnalytics{
		Reasons:             make(map[string]int64),
		AdaptationsOverTime: []AdaptationTimePoint{},
	}

	// Compter le nombre total d'adaptations
	var totalCount int64
	err := s.db.WithContext(ctx).Model(&models.BitrateAdaptationLog{}).
		Where("track_id = ?", trackID). // uuid.UUID
		Count(&totalCount).Error
	if err != nil {
		s.logger.Error("Failed to count adaptations", zap.Error(err), zap.String("track_id", trackID.String()))
		return nil, fmt.Errorf("failed to get analytics: %w", err)
	}
	analytics.TotalAdaptations = totalCount

	// Compter par raison
	type ReasonCount struct {
		Reason string
		Count  int64
	}
	var reasonCounts []ReasonCount
	err = s.db.WithContext(ctx).Model(&models.BitrateAdaptationLog{}).
		Select("reason, COUNT(*) as count").
		Where("track_id = ?", trackID). // uuid.UUID
		Group("reason").
		Scan(&reasonCounts).Error
	if err != nil {
		s.logger.Error("Failed to get reason counts", zap.Error(err), zap.String("track_id", trackID.String()))
		return nil, fmt.Errorf("failed to get analytics: %w", err)
	}

	for _, rc := range reasonCounts {
		analytics.Reasons[rc.Reason] = rc.Count
	}

	// Calculer la moyenne de bande passante (si disponible)
	var avgBandwidth *float64
	var avgResult struct {
		Avg float64
	}
	err = s.db.WithContext(ctx).Model(&models.BitrateAdaptationLog{}).
		Select("AVG(network_bandwidth) as avg").
		Where("track_id = ? AND network_bandwidth IS NOT NULL", trackID). // uuid.UUID
		Scan(&avgResult).Error
	if err == nil && avgResult.Avg > 0 {
		avgBandwidth = &avgResult.Avg
		analytics.AverageBandwidth = avgBandwidth
	}

	// Évolution dans le temps (groupé par jour)
	// Récupérer tous les logs et grouper par jour en Go pour compatibilité SQLite/PostgreSQL
	var logs []models.BitrateAdaptationLog
	err = s.db.WithContext(ctx).Model(&models.BitrateAdaptationLog{}).
		Where("track_id = ?", trackID). // uuid.UUID
		Order("created_at ASC").
		Find(&logs).Error
	if err == nil && len(logs) > 0 {
		// Grouper par jour
		dayCounts := make(map[string]int64)
		for _, log := range logs {
			// Extraire la date (YYYY-MM-DD)
			dateStr := log.CreatedAt.Format("2006-01-02")
			dayCounts[dateStr]++
		}

		// Convertir en slice triée
		type DayCount struct {
			Date  string
			Count int64
		}
		var sortedDays []DayCount
		for date, count := range dayCounts {
			sortedDays = append(sortedDays, DayCount{Date: date, Count: count})
		}

		// Trier par date (tri simple)
		for i := 0; i < len(sortedDays)-1; i++ {
			for j := i + 1; j < len(sortedDays); j++ {
				if sortedDays[i].Date > sortedDays[j].Date {
					sortedDays[i], sortedDays[j] = sortedDays[j], sortedDays[i]
				}
			}
		}

		// Ajouter aux analytics
		for _, dc := range sortedDays {
			analytics.AdaptationsOverTime = append(analytics.AdaptationsOverTime, AdaptationTimePoint(dc))
		}
	} else if err != nil {
		s.logger.Warn("Failed to get adaptations over time", zap.Error(err))
		// Continuer sans les données temporelles
	}

	return analytics, nil
}

// intPtr retourne un pointeur vers un int
func intPtr(i int) *int {
	return &i
}
