package services

import (
	"context"
	"errors"
	"fmt"
	"github.com/google/uuid"
	"time"

	"go.uber.org/zap"
	"gorm.io/gorm"
	"veza-backend-api/internal/models"
	"veza-backend-api/internal/types"
)

// AnalyticsService gère les analytics de lecture de tracks
type AnalyticsService struct {
	db     *gorm.DB
	logger *zap.Logger
}

// GetDB retourne la connexion DB (pour accès direct dans les handlers si nécessaire)
func (s *AnalyticsService) GetDB() *gorm.DB {
	return s.db
}

// NewAnalyticsService crée un nouveau service d'analytics
func NewAnalyticsService(db *gorm.DB, logger *zap.Logger) *AnalyticsService {
	if logger == nil {
		logger = zap.NewNop()
	}
	return &AnalyticsService{
		db:     db,
		logger: logger,
	}
}

// TrackStats est maintenant défini dans internal/types/stats.go
// Import: veza-backend-api/internal/types

// PlayTimePoint représente un point de données temporel pour les graphiques
type PlayTimePoint struct {
	Date  time.Time `json:"date"`
	Count int64     `json:"count"`
}

// TopTrack représente un track dans le classement
type TopTrack struct {
	TrackID         uuid.UUID `json:"track_id"` // Changed to uuid.UUID
	Title           string    `json:"title"`
	Artist          string    `json:"artist"`
	TotalPlays      int64     `json:"total_plays"`
	UniqueListeners int64     `json:"unique_listeners"`
	AverageDuration float64   `json:"average_duration"`
}

// UserStats est maintenant défini dans internal/types/stats.go
// Import: veza-backend-api/internal/types

// RecordPlay enregistre une lecture de track
// MIGRATION UUID: userID migré vers *uuid.UUID (nullable)
func (s *AnalyticsService) RecordPlay(ctx context.Context, trackID uuid.UUID, userID *uuid.UUID, duration int, device, ipAddress string) error {
	// Vérifier que le track existe
	var track models.Track
	if err := s.db.WithContext(ctx).First(&track, "id = ?", trackID).Error; err != nil { // Updated query to use "id = ?" for UUID
		if err == gorm.ErrRecordNotFound {
			return errors.New("track not found")
		}
		return fmt.Errorf("failed to check track: %w", err)
	}

	play := &models.TrackPlay{
		TrackID:   trackID,
		UserID:    userID,
		Duration:  duration,
		PlayedAt:  time.Now(),
		Device:    device,
		IPAddress: ipAddress,
	}

	if err := s.db.WithContext(ctx).Create(play).Error; err != nil {
		return fmt.Errorf("failed to record play: %w", err)
	}

	s.logger.Info("Track play recorded",
		zap.Any("track_id", trackID), // Changed to zap.Any for uuid.UUID
		zap.Any("user_id", userID),
		zap.Int("duration", duration),
	)

	return nil
}

// GetTrackStats récupère les statistiques d'un track
func (s *AnalyticsService) GetTrackStats(ctx context.Context, trackID uuid.UUID) (*types.TrackStats, error) { // Changed trackID to uuid.UUID
	var stats types.TrackStats

	// Vérifier que le track existe
	var track models.Track
	if err := s.db.WithContext(ctx).First(&track, "id = ?", trackID).Error; err != nil { // Updated query
		if err == gorm.ErrRecordNotFound {
			return nil, errors.New("track not found")
		}
		return nil, fmt.Errorf("failed to get track: %w", err)
	}

	// Total plays
	if err := s.db.WithContext(ctx).Model(&models.TrackPlay{}).
		Where("track_id = ?", trackID).
		Count(&stats.TotalPlays).Error; err != nil {
		return nil, fmt.Errorf("failed to count total plays: %w", err)
	}

	// Unique listeners (distinct user_id, en excluant NULL)
	if err := s.db.WithContext(ctx).Model(&models.TrackPlay{}).
		Where("track_id = ? AND user_id IS NOT NULL", trackID).
		Distinct("user_id").
		Count(&stats.UniqueListeners).Error; err != nil {
		return nil, fmt.Errorf("failed to count unique listeners: %w", err)
	}

	// Average duration
	var avgDuration float64
	if err := s.db.WithContext(ctx).Model(&models.TrackPlay{}).
		Where("track_id = ?", trackID).
		Select("COALESCE(AVG(duration), 0)").
		Scan(&avgDuration).Error; err != nil {
		return nil, fmt.Errorf("failed to calculate average duration: %w", err)
	}
	stats.AverageDuration = avgDuration

	// Completion rate (90% de la durée du track)
	if track.Duration > 0 && stats.TotalPlays > 0 {
		var completedPlays int64
		completionThreshold := int(float64(track.Duration) * 0.9)
		if err := s.db.WithContext(ctx).Model(&models.TrackPlay{}).
			Where("track_id = ? AND duration >= ?", trackID, completionThreshold).
			Count(&completedPlays).Error; err != nil {
			return nil, fmt.Errorf("failed to count completed plays: %w", err)
		}
		stats.CompletionRate = float64(completedPlays) / float64(stats.TotalPlays) * 100
	}

	return &stats, nil
}

// GetPlaysOverTime récupère les lectures sur une période pour un graphique temporel
func (s *AnalyticsService) GetPlaysOverTime(ctx context.Context, trackID uuid.UUID, startDate, endDate time.Time, interval string) ([]PlayTimePoint, error) { // Changed trackID to uuid.UUID
	// Vérifier que le track existe
	var track models.Track
	if err := s.db.WithContext(ctx).First(&track, "id = ?", trackID).Error; err != nil { // Updated query
		if err == gorm.ErrRecordNotFound {
			return nil, errors.New("track not found")
		}
		return nil, fmt.Errorf("failed to get track: %w", err)
	}

	// Requête SQL pour grouper par intervalle
	// Utiliser strftime pour SQLite (compatible avec la plupart des bases de données)
	var dateFormatSQLite string
	switch interval {
	case "hour":
		dateFormatSQLite = "%Y-%m-%d %H:00:00"
	case "day":
		dateFormatSQLite = "%Y-%m-%d"
	case "week":
		dateFormatSQLite = "%Y-W%W"
	case "month":
		dateFormatSQLite = "%Y-%m"
	default:
		dateFormatSQLite = "%Y-%m-%d"
	}

	var sqliteResults []struct {
		Date  string `gorm:"column:date"`
		Count int64  `gorm:"column:count"`
	}

	if err := s.db.WithContext(ctx).Model(&models.TrackPlay{}).
		Select(fmt.Sprintf("strftime('%s', played_at) as date, COUNT(*) as count", dateFormatSQLite)).
		Where("track_id = ? AND played_at >= ? AND played_at <= ?", trackID, startDate, endDate).
		Group("date").
		Order("date ASC").
		Scan(&sqliteResults).Error; err != nil {
		return nil, fmt.Errorf("failed to get plays over time: %w", err)
	}

	// Convertir les résultats
	points := make([]PlayTimePoint, len(sqliteResults))
	for i, r := range sqliteResults {
		// Essayer de parser avec différents formats
		parsedDate, err := time.Parse("2006-01-02 15:04:05", r.Date)
		if err != nil {
			parsedDate, err = time.Parse("2006-01-02", r.Date)
			if err != nil {
				parsedDate, err = time.Parse("2006-01", r.Date)
				if err != nil {
					parsedDate, _ = time.Parse("2006-W01", r.Date)
				}
			}
		}
		points[i] = PlayTimePoint{
			Date:  parsedDate,
			Count: r.Count,
		}
	}

	return points, nil
}

// GetTopTracks récupère les tracks les plus écoutés
func (s *AnalyticsService) GetTopTracks(ctx context.Context, limit int, startDate, endDate *time.Time) ([]TopTrack, error) {
	if limit <= 0 {
		limit = 10
	}
	if limit > 100 {
		limit = 100
	}

	query := s.db.WithContext(ctx).Model(&models.TrackPlay{}).
		Select(`
			track_plays.track_id,
			tracks.title,
			tracks.artist,
			COUNT(*) as total_plays,
			COUNT(DISTINCT track_plays.user_id) as unique_listeners,
			COALESCE(AVG(track_plays.duration), 0) as average_duration
		`).
		Joins("JOIN tracks ON tracks.id = track_plays.track_id").
		Group("track_plays.track_id, tracks.title, tracks.artist")

	// Filtrer par date si fourni
	if startDate != nil {
		query = query.Where("track_plays.played_at >= ?", *startDate)
	}
	if endDate != nil {
		query = query.Where("track_plays.played_at <= ?", *endDate)
	}

	query = query.Order("total_plays DESC").Limit(limit)

	var results []TopTrack
	if err := query.Scan(&results).Error; err != nil {
		return nil, fmt.Errorf("failed to get top tracks: %w", err)
	}

	return results, nil
}

// GetUserStats récupère les statistiques d'un utilisateur
func (s *AnalyticsService) GetUserStats(ctx context.Context, userID uuid.UUID) (*types.UserStats, error) {
	// Vérifier que l'utilisateur existe
	var user models.User
	if err := s.db.WithContext(ctx).First(&user, userID).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, errors.New("user not found")
		}
		return nil, fmt.Errorf("failed to get user: %w", err)
	}

	var stats types.UserStats

	// Total plays
	if err := s.db.WithContext(ctx).Model(&models.TrackPlay{}).
		Where("user_id = ?", userID).
		Count(&stats.TotalPlays).Error; err != nil {
		return nil, fmt.Errorf("failed to count total plays: %w", err)
	}

	// Unique tracks
	if err := s.db.WithContext(ctx).Model(&models.TrackPlay{}).
		Where("user_id = ?", userID).
		Distinct("track_id").
		Count(&stats.UniqueTracks).Error; err != nil {
		return nil, fmt.Errorf("failed to count unique tracks: %w", err)
	}

	// Total duration
	var totalDuration int64
	if err := s.db.WithContext(ctx).Model(&models.TrackPlay{}).
		Where("user_id = ?", userID).
		Select("COALESCE(SUM(duration), 0)").
		Scan(&totalDuration).Error; err != nil {
		return nil, fmt.Errorf("failed to calculate total duration: %w", err)
	}
	stats.TotalDuration = totalDuration

	// Average duration
	if stats.TotalPlays > 0 {
		stats.AverageDuration = float64(totalDuration) / float64(stats.TotalPlays)
	}

	return &stats, nil
}
