package monitoring

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/google/uuid"

	"veza-backend-api/internal/models"
	"veza-backend-api/internal/services"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

// PlaybackAnalyticsMonitor gère le monitoring des analytics de playback
// T0386: Create Playback Analytics Monitoring
type PlaybackAnalyticsMonitor struct {
	db               *gorm.DB
	logger           *zap.Logger
	alertsService    *services.PlaybackAlertsService
	analyticsService *services.PlaybackAnalyticsService

	// Métriques Prometheus
	recordedEventsTotal    *prometheus.CounterVec
	recordedEventsDuration *prometheus.HistogramVec
	recordedEventsErrors   *prometheus.CounterVec
	activeSessions         prometheus.Gauge
	averageCompletionRate  prometheus.Gauge
	averagePlayTime        prometheus.Gauge
	alertsGenerated        *prometheus.CounterVec
	alertsActive           prometheus.Gauge

	// Métriques internes
	mu                 sync.RWMutex
	metrics            *PerformanceMetrics
	lastAlertCheck     time.Time
	alertCheckInterval time.Duration
}

// PerformanceMetrics représente les métriques de performance collectées
type PerformanceMetrics struct {
	TotalEventsRecorded   int64         `json:"total_events_recorded"`
	TotalEventsFailed     int64         `json:"total_events_failed"`
	AverageRecordLatency  time.Duration `json:"average_record_latency"`
	P95RecordLatency      time.Duration `json:"p95_record_latency"`
	P99RecordLatency      time.Duration `json:"p99_record_latency"`
	ActiveSessions        int64         `json:"active_sessions"`
	AverageCompletionRate float64       `json:"average_completion_rate"`
	AveragePlayTime       float64       `json:"average_play_time"`
	TotalAlertsGenerated  int64         `json:"total_alerts_generated"`
	ActiveAlerts          int64         `json:"active_alerts"`
	LastUpdated           time.Time     `json:"last_updated"`
}

// DashboardMetrics représente les métriques pour le dashboard de monitoring
type DashboardMetrics struct {
	Performance  *PerformanceMetrics `json:"performance"`
	RecentAlerts []services.Alert    `json:"recent_alerts"`
	TopTracks    []TrackMetrics      `json:"top_tracks"`
	ErrorRate    float64             `json:"error_rate"`
	SuccessRate  float64             `json:"success_rate"`
	Throughput   float64             `json:"throughput"` // Events per second
	Timestamp    time.Time           `json:"timestamp"`
}

// TrackMetrics représente les métriques pour un track spécifique
type TrackMetrics struct {
	TrackID           uuid.UUID `json:"track_id"`
	TrackTitle        string    `json:"track_title"`
	TotalSessions     int64     `json:"total_sessions"`
	AverageCompletion float64   `json:"average_completion"`
	AveragePlayTime   float64   `json:"average_play_time"`
	ErrorRate         float64   `json:"error_rate"`
}

// Metrics variables (package-level to ensure single registration)
var (
	metricsOnce            sync.Once
	recordedEventsTotal    *prometheus.CounterVec
	recordedEventsDuration *prometheus.HistogramVec
	recordedEventsErrors   *prometheus.CounterVec
	activeSessions         prometheus.Gauge
	averageCompletionRate  prometheus.Gauge
	averagePlayTime        prometheus.Gauge
	alertsGenerated        *prometheus.CounterVec
	alertsActive           prometheus.Gauge
)

// NewPlaybackAnalyticsMonitor crée un nouveau monitor pour les analytics de playback
// T0386: Create Playback Analytics Monitoring
func NewPlaybackAnalyticsMonitor(
	db *gorm.DB,
	logger *zap.Logger,
	alertsService *services.PlaybackAlertsService,
	analyticsService *services.PlaybackAnalyticsService,
) *PlaybackAnalyticsMonitor {
	if logger == nil {
		logger = zap.NewNop()
	}

	metricsOnce.Do(func() {
		recordedEventsTotal = promauto.NewCounterVec(
			prometheus.CounterOpts{
				Name: "veza_playback_analytics_events_total",
				Help: "Total number of playback analytics events recorded",
			},
			[]string{"status"},
		)

		recordedEventsDuration = promauto.NewHistogramVec(
			prometheus.HistogramOpts{
				Name:    "veza_playback_analytics_record_duration_seconds",
				Help:    "Duration of playback analytics recording in seconds",
				Buckets: []float64{0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1.0, 5.0},
			},
			[]string{"operation"},
		)

		recordedEventsErrors = promauto.NewCounterVec(
			prometheus.CounterOpts{
				Name: "veza_playback_analytics_errors_total",
				Help: "Total number of playback analytics recording errors",
			},
			[]string{"error_type"},
		)

		activeSessions = promauto.NewGauge(
			prometheus.GaugeOpts{
				Name: "veza_playback_analytics_active_sessions",
				Help: "Number of active playback sessions",
			},
		)

		averageCompletionRate = promauto.NewGauge(
			prometheus.GaugeOpts{
				Name: "veza_playback_analytics_average_completion_rate",
				Help: "Average completion rate across all playback sessions",
			},
		)

		averagePlayTime = promauto.NewGauge(
			prometheus.GaugeOpts{
				Name: "veza_playback_analytics_average_play_time_seconds",
				Help: "Average play time in seconds across all playback sessions",
			},
		)

		alertsGenerated = promauto.NewCounterVec(
			prometheus.CounterOpts{
				Name: "veza_playback_analytics_alerts_generated_total",
				Help: "Total number of playback analytics alerts generated",
			},
			[]string{"alert_type", "severity"},
		)

		alertsActive = promauto.NewGauge(
			prometheus.GaugeOpts{
				Name: "veza_playback_analytics_alerts_active",
				Help: "Number of active playback analytics alerts",
			},
		)
	})

	monitor := &PlaybackAnalyticsMonitor{
		db:                 db,
		logger:             logger,
		alertsService:      alertsService,
		analyticsService:   analyticsService,
		metrics:            &PerformanceMetrics{},
		alertCheckInterval: 5 * time.Minute,

		// Assign shared metrics
		recordedEventsTotal:    recordedEventsTotal,
		recordedEventsDuration: recordedEventsDuration,
		recordedEventsErrors:   recordedEventsErrors,
		activeSessions:         activeSessions,
		averageCompletionRate:  averageCompletionRate,
		averagePlayTime:        averagePlayTime,
		alertsGenerated:        alertsGenerated,
		alertsActive:           alertsActive,
	}

	return monitor
}

// RecordEvent enregistre un événement d'analytics et met à jour les métriques
// T0386: Create Playback Analytics Monitoring
func (m *PlaybackAnalyticsMonitor) RecordEvent(ctx context.Context, analytics *models.PlaybackAnalytics, duration time.Duration, err error) {
	// Mettre à jour les métriques Prometheus
	if err != nil {
		m.recordedEventsTotal.WithLabelValues("error").Inc()
		m.recordedEventsErrors.WithLabelValues("database").Inc()
	} else {
		m.recordedEventsTotal.WithLabelValues("success").Inc()
	}

	m.recordedEventsDuration.WithLabelValues("record").Observe(duration.Seconds())

	// Mettre à jour les métriques internes
	m.mu.Lock()
	defer m.mu.Unlock()

	if err != nil {
		m.metrics.TotalEventsFailed++
	} else {
		m.metrics.TotalEventsRecorded++
	}

	// Mettre à jour la latence moyenne (calcul simplifié)
	if m.metrics.TotalEventsRecorded > 0 {
		totalLatency := m.metrics.AverageRecordLatency * time.Duration(m.metrics.TotalEventsRecorded-1)
		m.metrics.AverageRecordLatency = (totalLatency + duration) / time.Duration(m.metrics.TotalEventsRecorded)
	} else {
		m.metrics.AverageRecordLatency = duration
	}

	m.metrics.LastUpdated = time.Now()
}

// RecordBatchEvent enregistre un événement batch et met à jour les métriques
// T0386: Create Playback Analytics Monitoring
func (m *PlaybackAnalyticsMonitor) RecordBatchEvent(ctx context.Context, count int, duration time.Duration, err error) {
	if err != nil {
		m.recordedEventsTotal.WithLabelValues("error").Add(float64(count))
		m.recordedEventsErrors.WithLabelValues("database").Inc()
	} else {
		m.recordedEventsTotal.WithLabelValues("success").Add(float64(count))
	}

	m.recordedEventsDuration.WithLabelValues("batch").Observe(duration.Seconds())

	m.mu.Lock()
	defer m.mu.Unlock()

	if err != nil {
		m.metrics.TotalEventsFailed += int64(count)
	} else {
		m.metrics.TotalEventsRecorded += int64(count)
	}

	m.metrics.LastUpdated = time.Now()
}

// UpdateMetrics met à jour les métriques depuis la base de données
// T0386: Create Playback Analytics Monitoring
func (m *PlaybackAnalyticsMonitor) UpdateMetrics(ctx context.Context) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	// Compter les sessions actives (sessions commencées dans les dernières 30 minutes)
	activeSessionsThreshold := time.Now().Add(-30 * time.Minute)
	var activeSessionsCount int64
	if err := m.db.WithContext(ctx).Model(&models.PlaybackAnalytics{}).
		Where("started_at > ? AND (ended_at IS NULL OR ended_at > ?)", activeSessionsThreshold, activeSessionsThreshold).
		Count(&activeSessionsCount).Error; err != nil {
		m.logger.Warn("Failed to count active sessions", zap.Error(err))
	} else {
		m.metrics.ActiveSessions = activeSessionsCount
		m.activeSessions.Set(float64(activeSessionsCount))
	}

	// Calculer le taux de complétion moyen
	var avgCompletion float64
	if err := m.db.WithContext(ctx).Model(&models.PlaybackAnalytics{}).
		Select("COALESCE(AVG(completion_rate), 0)").
		Where("completion_rate > 0").
		Scan(&avgCompletion).Error; err != nil {
		m.logger.Warn("Failed to calculate average completion rate", zap.Error(err))
	} else {
		m.metrics.AverageCompletionRate = avgCompletion
		m.averageCompletionRate.Set(avgCompletion)
	}

	// Calculer le temps de lecture moyen
	var avgPlayTime float64
	if err := m.db.WithContext(ctx).Model(&models.PlaybackAnalytics{}).
		Select("COALESCE(AVG(play_time), 0)").
		Where("play_time > 0").
		Scan(&avgPlayTime).Error; err != nil {
		m.logger.Warn("Failed to calculate average play time", zap.Error(err))
	} else {
		m.metrics.AveragePlayTime = avgPlayTime
		m.averagePlayTime.Set(avgPlayTime)
	}

	m.metrics.LastUpdated = time.Now()

	return nil
}

// CheckAlerts vérifie les alertes pour tous les tracks actifs
// T0386: Create Playback Analytics Monitoring
func (m *PlaybackAnalyticsMonitor) CheckAlerts(ctx context.Context) ([]services.Alert, error) {
	if m.alertsService == nil {
		return nil, fmt.Errorf("alerts service not available")
	}

	// Récupérer les tracks avec des sessions récentes (dernières 24 heures)
	recentThreshold := time.Now().Add(-24 * time.Hour)
	var trackIDs []uuid.UUID
	if err := m.db.WithContext(ctx).Model(&models.PlaybackAnalytics{}).
		Distinct("track_id").
		Where("started_at > ?", recentThreshold).
		Pluck("track_id", &trackIDs).Error; err != nil {
		return nil, fmt.Errorf("failed to get recent track IDs: %w", err)
	}

	allAlerts := make([]services.Alert, 0)
	for _, trackID := range trackIDs {
		alerts, err := m.alertsService.CheckAlerts(ctx, trackID, nil)
		if err != nil {
			m.logger.Warn("Failed to check alerts for track",
				zap.Error(err),
				zap.String("track_id", trackID.String()))
			continue
		}

		// Mettre à jour les métriques Prometheus
		for _, alert := range alerts {
			m.alertsGenerated.WithLabelValues(alert.Type, alert.Severity).Inc()
		}

		allAlerts = append(allAlerts, alerts...)
	}

	// Mettre à jour le nombre d'alertes actives
	m.mu.Lock()
	m.metrics.TotalAlertsGenerated += int64(len(allAlerts))
	m.metrics.ActiveAlerts = int64(len(allAlerts))
	m.mu.Unlock()

	m.alertsActive.Set(float64(len(allAlerts)))
	m.lastAlertCheck = time.Now()

	m.logger.Info("Checked playback analytics alerts",
		zap.Int("tracks_checked", len(trackIDs)),
		zap.Int("alerts_found", len(allAlerts)))

	return allAlerts, nil
}

// GetPerformanceMetrics retourne les métriques de performance actuelles
// T0386: Create Playback Analytics Monitoring
func (m *PlaybackAnalyticsMonitor) GetPerformanceMetrics() *PerformanceMetrics {
	m.mu.RLock()
	defer m.mu.RUnlock()

	// Retourner une copie pour éviter les modifications concurrentes
	return &PerformanceMetrics{
		TotalEventsRecorded:   m.metrics.TotalEventsRecorded,
		TotalEventsFailed:     m.metrics.TotalEventsFailed,
		AverageRecordLatency:  m.metrics.AverageRecordLatency,
		P95RecordLatency:      m.metrics.P95RecordLatency,
		P99RecordLatency:      m.metrics.P99RecordLatency,
		ActiveSessions:        m.metrics.ActiveSessions,
		AverageCompletionRate: m.metrics.AverageCompletionRate,
		AveragePlayTime:       m.metrics.AveragePlayTime,
		TotalAlertsGenerated:  m.metrics.TotalAlertsGenerated,
		ActiveAlerts:          m.metrics.ActiveAlerts,
		LastUpdated:           m.metrics.LastUpdated,
	}
}

// GetDashboardMetrics retourne les métriques complètes pour le dashboard
// T0386: Create Playback Analytics Monitoring
func (m *PlaybackAnalyticsMonitor) GetDashboardMetrics(ctx context.Context) (*DashboardMetrics, error) {
	// Mettre à jour les métriques depuis la base de données
	if err := m.UpdateMetrics(ctx); err != nil {
		m.logger.Warn("Failed to update metrics", zap.Error(err))
	}

	// Vérifier les alertes si nécessaire
	var recentAlerts []services.Alert
	if time.Since(m.lastAlertCheck) > m.alertCheckInterval {
		alerts, err := m.CheckAlerts(ctx)
		if err != nil {
			m.logger.Warn("Failed to check alerts", zap.Error(err))
		} else {
			recentAlerts = alerts
		}
	}

	// Récupérer les top tracks
	topTracks, err := m.getTopTracks(ctx, 10)
	if err != nil {
		m.logger.Warn("Failed to get top tracks", zap.Error(err))
		topTracks = []TrackMetrics{}
	}

	// Calculer les taux d'erreur et de succès
	perfMetrics := m.GetPerformanceMetrics()
	totalEvents := perfMetrics.TotalEventsRecorded + perfMetrics.TotalEventsFailed
	var errorRate, successRate float64
	if totalEvents > 0 {
		errorRate = float64(perfMetrics.TotalEventsFailed) / float64(totalEvents) * 100
		successRate = float64(perfMetrics.TotalEventsRecorded) / float64(totalEvents) * 100
	}

	// Calculer le throughput (événements par seconde sur la dernière heure)
	var throughput float64
	oneHourAgo := time.Now().Add(-1 * time.Hour)
	var eventsLastHour int64
	if err := m.db.WithContext(ctx).Model(&models.PlaybackAnalytics{}).
		Where("created_at > ?", oneHourAgo).
		Count(&eventsLastHour).Error; err == nil {
		throughput = float64(eventsLastHour) / 3600.0 // Events per second
	}

	return &DashboardMetrics{
		Performance:  perfMetrics,
		RecentAlerts: recentAlerts,
		TopTracks:    topTracks,
		ErrorRate:    errorRate,
		SuccessRate:  successRate,
		Throughput:   throughput,
		Timestamp:    time.Now(),
	}, nil
}

// getTopTracks récupère les métriques pour les tracks les plus actifs
// T0386: Create Playback Analytics Monitoring
func (m *PlaybackAnalyticsMonitor) getTopTracks(ctx context.Context, limit int) ([]TrackMetrics, error) {
	type TrackStats struct {
		TrackID           uuid.UUID `gorm:"column:track_id"`
		TrackTitle        string    `gorm:"column:track_title"`
		TotalSessions     int64     `gorm:"column:total_sessions"`
		AverageCompletion float64   `gorm:"column:average_completion"`
		AveragePlayTime   float64   `gorm:"column:average_play_time"`
		ErrorCount        int64     `gorm:"column:error_count"`
	}

	var stats []TrackStats

	// Utiliser GORM builder pour compatibilité SQLite/Postgres (évite NOW() - INTERVAL)
	oneDayAgo := time.Now().Add(-24 * time.Hour)

	if err := m.db.WithContext(ctx).
		Table("playback_analytics pa").
		Select(`
			pa.track_id,
			COALESCE(t.title, 'Unknown') as track_title,
			COUNT(*) as total_sessions,
			COALESCE(AVG(pa.completion_rate), 0) as average_completion,
			COALESCE(AVG(pa.play_time), 0) as average_play_time,
			0 as error_count
		`).
		Joins("LEFT JOIN tracks t ON pa.track_id = t.id").
		Where("pa.created_at > ?", oneDayAgo).
		Group("pa.track_id, t.title").
		Order("total_sessions DESC").
		Limit(limit).
		Scan(&stats).Error; err != nil {
		return nil, fmt.Errorf("failed to get top tracks: %w", err)
	}

	trackMetrics := make([]TrackMetrics, 0, len(stats))
	for _, stat := range stats {
		var errorRate float64
		if stat.TotalSessions > 0 {
			errorRate = float64(stat.ErrorCount) / float64(stat.TotalSessions) * 100
		}

		trackMetrics = append(trackMetrics, TrackMetrics{
			TrackID:           stat.TrackID,
			TrackTitle:        stat.TrackTitle,
			TotalSessions:     stat.TotalSessions,
			AverageCompletion: stat.AverageCompletion,
			AveragePlayTime:   stat.AveragePlayTime,
			ErrorRate:         errorRate,
		})
	}

	return trackMetrics, nil
}

// StartBackgroundMonitoring démarre le monitoring en arrière-plan
// T0386: Create Playback Analytics Monitoring
func (m *PlaybackAnalyticsMonitor) StartBackgroundMonitoring(ctx context.Context, updateInterval time.Duration) {
	ticker := time.NewTicker(updateInterval)
	defer ticker.Stop()

	// Mettre à jour immédiatement au démarrage
	if err := m.UpdateMetrics(ctx); err != nil {
		m.logger.Error("Failed to update metrics on startup", zap.Error(err))
	}

	for {
		select {
		case <-ctx.Done():
			m.logger.Info("Stopping playback analytics monitoring")
			return
		case <-ticker.C:
			if err := m.UpdateMetrics(ctx); err != nil {
				m.logger.Error("Failed to update metrics", zap.Error(err))
			}

			// Vérifier les alertes périodiquement
			if time.Since(m.lastAlertCheck) > m.alertCheckInterval {
				if _, err := m.CheckAlerts(ctx); err != nil {
					m.logger.Error("Failed to check alerts", zap.Error(err))
				}
			}
		}
	}
}
