import { apiClient } from '@/services/api/client';
import { logger } from '@/utils/logger';
import { parseApiError } from '@/utils/apiErrorHandler';

/**
 * Service pour les analytics de lecture
 * T0359: Create Playback Analytics Frontend Service
 * T0364: Create Playback Analytics Dashboard Component
 * T0371: Create Playback Analytics Summary Component
 * T0377: Create Playback Analytics Heatmap Component
 * T0385: Create Playback Analytics Error Handling
 */

/**
 * Interface pour un événement d'analytics de lecture
 */
export interface PlaybackEvent {
  play_time: number; // seconds
  pause_count?: number; // optional, default 0
  seek_count?: number; // optional, default 0
  completion_rate?: number; // optional, will be calculated if not provided (0-100)
  started_at: string; // ISO 8601 format
  ended_at?: string; // optional, ISO 8601 format
}

/**
 * Interface pour la réponse d'enregistrement d'analytics
 */
export interface RecordAnalyticsResponse {
  status: string;
  id: string;
}

/**
 * Interface pour les statistiques de lecture
 * T0364: Create Playback Analytics Dashboard Component
 */
export interface PlaybackStats {
  total_sessions: number;
  total_play_time: number; // seconds
  average_play_time: number; // seconds
  total_pauses: number;
  average_pauses: number;
  total_seeks: number;
  average_seeks: number;
  average_completion: number; // percentage
  completion_rate: number; // percentage of sessions with >90% completion
}

/**
 * Interface pour les tendances d'analytics
 * T0364: Create Playback Analytics Dashboard Component
 */
export interface TrendsData {
  play_time_trend: number; // % de changement sur 7 jours
  completion_trend: number; // % de changement sur 7 jours
  sessions_trend: number; // % de changement sur 7 jours
  average_play_time: number; // Moyenne sur 7 jours
  average_completion: number; // Moyenne sur 7 jours
  total_sessions_7days: number; // Total sur 7 jours
  total_sessions_30days: number; // Total sur 30 jours
}

/**
 * Interface pour un point dans une série temporelle
 * T0364: Create Playback Analytics Dashboard Component
 */
export interface TimeSeriesPoint {
  date: string; // Format: YYYY-MM-DD
  sessions: number;
  total_play_time: number; // seconds
  average_play_time: number; // seconds
  average_completion: number; // percentage
}

/**
 * Interface pour les données du dashboard
 * T0364: Create Playback Analytics Dashboard Component
 */
export interface PlaybackDashboardData {
  stats: PlaybackStats;
  trends: TrendsData;
  time_series: TimeSeriesPoint[];
}

/**
 * Interface pour le résumé des analytics de lecture
 * T0371: Create Playback Analytics Summary Component
 */
export interface PlaybackSummary {
  total_plays: number; // Nombre total de lectures
  completion_rate: number; // Taux de complétion moyen (%)
  average_play_time: number; // Temps de lecture moyen (secondes)
}

/**
 * Interface pour un segment de heatmap
 * T0377: Create Playback Analytics Heatmap Component
 */
export interface HeatmapSegment {
  start_time: number; // Temps de début du segment (secondes)
  end_time: number; // Temps de fin du segment (secondes)
  listen_count: number; // Nombre de fois que ce segment a été écouté
  skip_count: number; // Nombre de fois que ce segment a été sauté
  intensity: number; // Intensité d'écoute (0-1, normalisée)
  average_play_time: number; // Temps de lecture moyen dans ce segment (secondes)
}

/**
 * Interface pour les données de heatmap
 * T0377: Create Playback Analytics Heatmap Component
 */
export interface PlaybackHeatmap {
  track_id: string;
  track_duration: number; // secondes
  segment_size: number; // Taille des segments (secondes)
  total_sessions: number;
  segments: HeatmapSegment[];
  max_intensity: number; // Intensité maximale (pour normalisation)
  generated_at: string; // ISO 8601 format
}

/**
 * Configuration pour le retry
 * T0385: Create Playback Analytics Error Handling
 */
interface RetryConfig {
  maxRetries: number;
  initialDelay: number; // ms
  maxDelay: number; // ms
  backoffMultiplier: number;
}

const DEFAULT_RETRY_CONFIG: RetryConfig = {
  maxRetries: 3,
  initialDelay: 1000, // 1 seconde
  maxDelay: 10000, // 10 secondes
  backoffMultiplier: 2,
};

/**
 * Clé pour le stockage local des analytics en attente
 * T0385: Create Playback Analytics Error Handling
 */
const PENDING_ANALYTICS_STORAGE_KEY = 'veza_pending_playback_analytics';

/**
 * Interface pour un événement d'analytics en attente dans le localStorage
 * T0385: Create Playback Analytics Error Handling
 */
interface PendingAnalyticsEvent {
  trackId: string;
  event: PlaybackEvent;
  timestamp: number; // Timestamp de création
  retryCount: number;
}

/**
 * Retry avec exponential backoff
 * T0385: Create Playback Analytics Error Handling
 */
async function retryWithBackoff<T>(
  fn: () => Promise<T>,
  config: RetryConfig = DEFAULT_RETRY_CONFIG,
  onRetry?: (attempt: number, error: Error) => void,
): Promise<T> {
  let lastError: Error | null = null;
  let delay = config.initialDelay;

  for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error: unknown) {
      lastError = error instanceof Error ? error : new Error(String(error));
      const apiError = parseApiError(error);
      const code =
        typeof apiError.code === 'string'
          ? parseInt(apiError.code, 10)
          : apiError.code;

      // Ne pas retry pour les erreurs 400 (bad request) ou 401 (unauthorized)
      if (code === 400 || code === 401 || code === 404) {
        throw lastError; // Ne pas retry pour ces erreurs
      }

      if (attempt < config.maxRetries) {
        if (onRetry) {
          onRetry(attempt + 1, lastError);
        }

        // Attendre avant de retry
        await new Promise((resolve) => setTimeout(resolve, delay));

        // Exponential backoff
        delay = Math.min(delay * config.backoffMultiplier, config.maxDelay);
      }
    }
  }

  throw lastError || new Error('Max retries exceeded');
}

/**
 * Sauvegarde un événement d'analytics dans le localStorage comme fallback
 * T0385: Create Playback Analytics Error Handling
 */
function saveToLocalStorage(trackId: string, event: PlaybackEvent): void {
  try {
    const pending = getPendingAnalytics();
    const pendingEvent: PendingAnalyticsEvent = {
      trackId,
      event,
      timestamp: Date.now(),
      retryCount: 0,
    };
    pending.push(pendingEvent);

    // Limiter à 100 événements en attente pour éviter de saturer le localStorage
    if (pending.length > 100) {
      pending.shift(); // Supprimer le plus ancien
    }

    localStorage.setItem(
      PENDING_ANALYTICS_STORAGE_KEY,
      JSON.stringify(pending),
    );
    logger.info(
      `[PlaybackAnalytics] Saved event to localStorage (${pending.length} pending)`,
      {
        trackId,
        playTime: event.play_time,
      },
    );
  } catch (error: unknown) {
    const message = error instanceof Error ? error.message : String(error);
    logger.error('[PlaybackAnalytics] Failed to save to localStorage:', {
      error: message,
    });
  }
}

/**
 * Récupère les événements d'analytics en attente depuis le localStorage
 * T0385: Create Playback Analytics Error Handling
 */
function getPendingAnalytics(): PendingAnalyticsEvent[] {
  try {
    const data = localStorage.getItem(PENDING_ANALYTICS_STORAGE_KEY);
    if (!data) {
      return [];
    }
    return JSON.parse(data) as PendingAnalyticsEvent[];
  } catch (error: unknown) {
    const message = error instanceof Error ? error.message : String(error);
    logger.error('[PlaybackAnalytics] Failed to read from localStorage:', {
      error: message,
    });
    return [];
  }
}

/**
 * Tente de réenvoyer les événements en attente
 * T0385: Create Playback Analytics Error Handling
 */
export async function retryPendingAnalytics(): Promise<number> {
  const pending = getPendingAnalytics();
  if (pending.length === 0) {
    return 0;
  }

  let successCount = 0;
  const toRemove: number[] = [];

  for (let i = pending.length - 1; i >= 0; i--) {
    const pendingEvent = pending[i];

    // Ne pas retry les événements trop anciens (> 7 jours)
    const age = Date.now() - pendingEvent.timestamp;
    if (age > 7 * 24 * 60 * 60 * 1000) {
      toRemove.push(i);
      continue;
    }

    try {
      await recordPlaybackEvent(pendingEvent.trackId, pendingEvent.event, {
        skipRetry: true, // Ne pas retry à nouveau
        skipFallback: true, // Ne pas sauvegarder dans localStorage
      });
      toRemove.push(i);
      successCount++;
    } catch (error) {
      // Incrémenter le compteur de retry
      pendingEvent.retryCount++;

      // Mettre à jour l'événement dans le tableau
      pending[i] = pendingEvent;

      // Supprimer si trop de retries
      if (pendingEvent.retryCount >= 5) {
        toRemove.push(i);
        logger.warn(
          `[PlaybackAnalytics] Removed event after ${pendingEvent.retryCount} failed retries`,
          {
            trackId: pendingEvent.trackId,
          },
        );
      } else {
        // Sauvegarder le retryCount mis à jour seulement si on ne va pas le supprimer
        // (on le sauvegarde maintenant car on va mettre à jour localStorage à la fin)
      }
    }
  }

  // Mettre à jour le localStorage avec les modifications (retryCount mis à jour et événements supprimés)
  // Toujours mettre à jour si on a des modifications (retryCount ou suppressions)
  if (
    toRemove.length > 0 ||
    pending.some((p, idx) => !toRemove.includes(idx) && p.retryCount > 0)
  ) {
    // Filtrer les événements à supprimer et garder ceux qui restent avec leurs retryCount mis à jour
    const updatedPending = pending.filter(
      (_, index) => !toRemove.includes(index),
    );
    try {
      localStorage.setItem(
        PENDING_ANALYTICS_STORAGE_KEY,
        JSON.stringify(updatedPending),
      );
    } catch (error: unknown) {
      const message = error instanceof Error ? error.message : String(error);
      logger.error(
        '[PlaybackAnalytics] Failed to update localStorage after processing events:',
        { error: message },
      );
    }
  } else if (pending.length > 0) {
    // Même si pas de modifications, s'assurer que le localStorage est à jour
    try {
      localStorage.setItem(
        PENDING_ANALYTICS_STORAGE_KEY,
        JSON.stringify(pending),
      );
    } catch (error: unknown) {
      const message = error instanceof Error ? error.message : String(error);
      logger.error('[PlaybackAnalytics] Failed to update localStorage:', {
        error: message,
      });
    }
  }

  if (successCount > 0) {
    logger.info(`[PlaybackAnalytics] Retried ${successCount} pending events`);
  }

  return successCount;
}

/**
 * Options pour l'enregistrement d'analytics
 * T0385: Create Playback Analytics Error Handling
 */
interface RecordOptions {
  skipRetry?: boolean;
  skipFallback?: boolean;
  onError?: (error: Error) => void;
  onRetry?: (attempt: number, error: Error) => void;
}

/**
 * Enregistre un événement d'analytics de lecture pour un track
 * T0385: Create Playback Analytics Error Handling - Amélioré avec retry et fallback
 * @param trackId - ID du track
 * @param event - Données de l'événement de lecture
 * @param options - Options pour l'enregistrement
 * @returns Réponse avec le statut et l'ID de l'analytics créé
 * @throws Error si la requête échoue après tous les retries
 */
export async function recordPlaybackEvent(
  trackId: string,
  event: PlaybackEvent,
  options: RecordOptions = {},
): Promise<RecordAnalyticsResponse> {
  const { skipRetry = false, skipFallback = false, onError, onRetry } = options;

  const attemptRequest = async (): Promise<RecordAnalyticsResponse> => {
    try {
      const response = await apiClient.post<RecordAnalyticsResponse>(
        `/tracks/${trackId}/playback/analytics`,
        event,
      );
      return response.data;
    } catch (error: unknown) {
      const apiError = parseApiError(error);
      const { message } = apiError;
      const code =
        typeof apiError.code === 'string'
          ? parseInt(apiError.code, 10)
          : apiError.code;

      // Logger l'erreur
      logger.error('[PlaybackAnalytics] Failed to record event:', {
        trackId,
        playTime: event.play_time,
        error: message,
        status: code,
        timestamp: new Date().toISOString(),
      });

      // Gérer les erreurs spécifiques
      if (code === 401) {
        const err = new Error(
          'Unauthorized: Please log in to record playback analytics',
        );
        if (onError) onError(err);
        throw err;
      } else if (code === 400) {
        const err = new Error(`Invalid request: ${message}`);
        if (onError) onError(err);
        throw err;
      } else if (code === 404) {
        const err = new Error(`Track not found: ${trackId}`);
        if (onError) onError(err);
        throw err;
      } else if (code && code >= 500) {
        const err = new Error(`Server error: ${message}`);
        throw err;
      } else if (code && code > 0) {
        const err = new Error(`Request failed: ${message}`);
        if (onError) onError(err);
        throw err;
      } else if (message.includes('Network error')) {
        const err = new Error('Network error: Unable to reach the server');
        throw err;
      } else {
        const err = new Error(`Error: ${message}`);
        if (onError) onError(err);
        throw err;
      }
    }
  };

  try {
    if (skipRetry) {
      return await attemptRequest();
    }

    // Retry avec exponential backoff
    return await retryWithBackoff(
      attemptRequest,
      DEFAULT_RETRY_CONFIG,
      onRetry,
    );
  } catch (error: unknown) {
    const finalError =
      error instanceof Error ? error : new Error(String(error));

    // Fallback: sauvegarder dans localStorage si l'option n'est pas désactivée
    if (!skipFallback) {
      saveToLocalStorage(trackId, event);

      // Notifier l'utilisateur (optionnel, via callback)
      if (onError) {
        onError(
          new Error(
            `Failed to record analytics. Event saved locally and will be retried later. Original error: ${finalError.message}`,
          ),
        );
      }
    } else {
      if (onError) {
        onError(finalError);
      }
    }

    throw finalError;
  }
}

/**
 * Récupère les données du dashboard d'analytics de lecture pour un track
 * T0364: Create Playback Analytics Dashboard Component
 * T0385: Create Playback Analytics Error Handling - Amélioré avec retry
 * @param trackId - ID du track
 * @returns Données du dashboard (statistiques, tendances, séries temporelles)
 * @throws Error si la requête échoue
 */
export async function getPlaybackDashboard(
  trackId: string,
): Promise<PlaybackDashboardData> {
  const attemptRequest = async (): Promise<PlaybackDashboardData> => {
    try {
      const response = await apiClient.get<{
        dashboard: PlaybackDashboardData;
      }>(`/tracks/${trackId}/playback/dashboard`);
      return response.data.dashboard;
    } catch (error: unknown) {
      const apiError = parseApiError(error);
      const { message } = apiError;
      const code =
        typeof apiError.code === 'string'
          ? parseInt(apiError.code, 10)
          : apiError.code;

      // Logger l'erreur
      logger.error('[PlaybackAnalytics] Failed to get dashboard:', {
        trackId,
        error: message,
        status: code,
        timestamp: new Date().toISOString(),
      });

      // Gérer les erreurs spécifiques
      if (code === 401) {
        throw new Error(
          'Unauthorized: Please log in to view playback analytics',
        );
      } else if (code === 400) {
        throw new Error(`Invalid request: ${message}`);
      } else if (code === 404) {
        throw new Error(`Track not found: ${trackId}`);
      } else if (code && code >= 500) {
        throw new Error(`Server error: ${message}`);
      } else if (code && code > 0) {
        throw new Error(`Request failed: ${message}`);
      } else if (message.includes('Network error')) {
        throw new Error('Network error: Unable to reach the server');
      } else {
        throw new Error(`Error: ${message}`);
      }
    }
  };

  // Retry avec exponential backoff pour les erreurs réseau/serveur
  return await retryWithBackoff(attemptRequest, DEFAULT_RETRY_CONFIG);
}

/**
 * Récupère le résumé des analytics de lecture pour un track
 * T0371: Create Playback Analytics Summary Component
 * T0385: Create Playback Analytics Error Handling - Amélioré avec retry
 * @param trackId - ID du track
 * @returns Résumé des analytics (total plays, completion rate, average play time)
 * @throws Error si la requête échoue
 */
export async function getPlaybackSummary(
  trackId: string,
): Promise<PlaybackSummary> {
  const attemptRequest = async (): Promise<PlaybackSummary> => {
    try {
      const response = await apiClient.get<{ summary: PlaybackSummary }>(
        `/tracks/${trackId}/playback/summary`,
      );
      return response.data.summary;
    } catch (error: unknown) {
      const apiError = parseApiError(error);
      const { message } = apiError;
      const code =
        typeof apiError.code === 'string'
          ? parseInt(apiError.code, 10)
          : apiError.code;

      // Logger l'erreur
      logger.error('[PlaybackAnalytics] Failed to get summary:', {
        trackId,
        error: message,
        status: code,
        timestamp: new Date().toISOString(),
      });

      // Gérer les erreurs spécifiques
      if (code === 401) {
        throw new Error('Unauthorized: Please log in to view playback summary');
      } else if (code === 400) {
        throw new Error(`Invalid request: ${message}`);
      } else if (code === 404) {
        throw new Error(`Track not found: ${trackId}`);
      } else if (code && code >= 500) {
        throw new Error(`Server error: ${message}`);
      } else if (code && code > 0) {
        throw new Error(`Request failed: ${message}`);
      } else if (message.includes('Network error')) {
        throw new Error('Network error: Unable to reach the server');
      } else {
        throw new Error(`Error: ${message}`);
      }
    }
  };

  // Retry avec exponential backoff pour les erreurs réseau/serveur
  return await retryWithBackoff(attemptRequest, DEFAULT_RETRY_CONFIG);
}

/**
 * Récupère les données de heatmap pour un track
 * T0377: Create Playback Analytics Heatmap Component
 * T0385: Create Playback Analytics Error Handling - Amélioré avec retry
 * @param trackId - ID du track
 * @param segmentSize - Taille des segments en secondes (optionnel, défaut: 5)
 * @returns Données de heatmap (segments avec intensités, zones écoutées, zones skip)
 * @throws Error si la requête échoue
 */
export async function getPlaybackHeatmap(
  trackId: string,
  segmentSize?: number,
): Promise<PlaybackHeatmap> {
  const attemptRequest = async (): Promise<PlaybackHeatmap> => {
    try {
      const params = new URLSearchParams();
      if (segmentSize !== undefined && segmentSize > 0) {
        params.append('segment_size', segmentSize.toString());
      }
      const queryString = params.toString();
      const url = `/tracks/${trackId}/playback/heatmap${queryString ? `?${queryString}` : ''}`;

      const response = await apiClient.get<{ heatmap: PlaybackHeatmap }>(url);
      return response.data.heatmap;
    } catch (error: unknown) {
      const apiError = parseApiError(error);
      const { message } = apiError;
      const code =
        typeof apiError.code === 'string'
          ? parseInt(apiError.code, 10)
          : apiError.code;

      // Logger l'erreur
      logger.error('[PlaybackAnalytics] Failed to get heatmap:', {
        trackId,
        segmentSize,
        error: message,
        status: code,
        timestamp: new Date().toISOString(),
      });

      // Gérer les erreurs spécifiques
      if (code === 401) {
        throw new Error('Unauthorized: Please log in to view playback heatmap');
      } else if (code === 400) {
        throw new Error(`Invalid request: ${message}`);
      } else if (code === 404) {
        throw new Error(`Track not found: ${trackId}`);
      } else if (code && code >= 500) {
        throw new Error(`Server error: ${message}`);
      } else if (code && code > 0) {
        throw new Error(`Request failed: ${message}`);
      } else if (message.includes('Network error')) {
        throw new Error('Network error: Unable to reach the server');
      } else {
        throw new Error(`Error: ${message}`);
      }
    }
  };

  // Retry avec exponential backoff pour les erreurs réseau/serveur
  return await retryWithBackoff(attemptRequest, DEFAULT_RETRY_CONFIG);
}
