"""Lecture MP3 avec génération RMS pour jaw sync."""

import os
import time
import threading
import numpy as np
from typing import Optional, Generator, Callable
from pydub import AudioSegment
from pydub.playback import play
import pygame

from .voice_types import MP3Request, PlaybackState, RMSData
from .config import VoiceConfig
from .logger import setup_logger, log_with_metrics
from .jaw_sync import JawSyncProcessor


class MP3Player:
    """Lecteur MP3 avec génération RMS temps réel."""

    def __init__(self, jaw_processor: JawSyncProcessor):
        """Initialise le lecteur MP3."""
        self.jaw_processor = jaw_processor
        self.logger = setup_logger(__name__)

        # État de lecture
        self.state = PlaybackState.IDLE
        self.current_file: Optional[str] = None
        self.loop_enabled = False

        # Threading
        self._play_thread: Optional[threading.Thread] = None
        self._stop_event = threading.Event()
        self._pause_event = threading.Event()

        # Callbacks
        self.rms_callback: Optional[Callable[[RMSData], None]] = None

        # Initialise pygame mixer pour un meilleur contrôle
        pygame.mixer.pre_init(
            frequency=VoiceConfig.SAMPLE_RATE, size=-16, channels=2, buffer=1024
        )
        pygame.mixer.init()

        self._lock = threading.Lock()

    def set_rms_callback(self, callback: Callable[[RMSData], None]):
        """Définit le callback pour les données RMS."""
        self.rms_callback = callback

    def play(self, request: MP3Request) -> bool:
        """Lance la lecture d'un fichier MP3."""
        if not os.path.exists(request.file):
            self.logger.error(f"Fichier MP3 introuvable: {request.file}")
            return False

        # Stop lecture actuelle si nécessaire
        self.stop()

        with self._lock:
            self.current_file = request.file
            self.loop_enabled = request.loop
            self.state = PlaybackState.PLAYING
            self._stop_event.clear()
            self._pause_event.clear()

        # Lance lecture en thread séparé
        self._play_thread = threading.Thread(
            target=self._play_loop, args=(request.file,), daemon=True
        )
        self._play_thread.start()

        self.logger.info(f"Lecture MP3 démarrée: {request.file}")
        return True

    def pause(self):
        """Met en pause la lecture."""
        with self._lock:
            if self.state == PlaybackState.PLAYING:
                self.state = PlaybackState.PAUSED
                self._pause_event.set()
                pygame.mixer.music.pause()
                self.logger.info("Lecture MP3 en pause")

    def resume(self):
        """Reprend la lecture."""
        with self._lock:
            if self.state == PlaybackState.PAUSED:
                self.state = PlaybackState.PLAYING
                self._pause_event.clear()
                pygame.mixer.music.unpause()
                self.logger.info("Lecture MP3 reprise")

    def stop(self):
        """Arrête la lecture."""
        with self._lock:
            if self.state != PlaybackState.IDLE:
                self.state = PlaybackState.IDLE
                self._stop_event.set()
                self._pause_event.clear()
                pygame.mixer.music.stop()

                if self._play_thread and self._play_thread.is_alive():
                    self._play_thread.join(timeout=2.0)

                self.current_file = None
                self.logger.info("Lecture MP3 arrêtée")

    def get_state(self) -> PlaybackState:
        """Retourne l'état actuel."""
        return self.state

    def _play_loop(self, file_path: str):
        """Boucle principale de lecture."""
        try:
            start_time = time.time()

            # Charge le fichier MP3
            audio_segment = AudioSegment.from_mp3(file_path)

            load_time = (time.time() - start_time) * 1000
            log_with_metrics(
                self.logger,
                "INFO",
                f"MP3 chargé: {os.path.basename(file_path)}",
                load_time_ms=load_time,
                duration_ms=len(audio_segment),
                sample_rate=audio_segment.frame_rate,
            )

            # Boucle de lecture (avec loop si activé)
            while not self._stop_event.is_set():
                self._play_segment(audio_segment)

                if not self.loop_enabled or self._stop_event.is_set():
                    break

                # Petite pause entre les répétitions
                time.sleep(0.1)

        except Exception as e:
            self.logger.error(f"Erreur lecture MP3: {e}")
        finally:
            with self._lock:
                self.state = PlaybackState.IDLE
                self.current_file = None

    def _play_segment(self, audio_segment: AudioSegment):
        """Lit un segment audio avec génération RMS."""
        try:
            # Convertit en numpy pour traitement RMS
            audio_data = self._segment_to_numpy(audio_segment)

            # Lance pygame pour la lecture audio
            temp_file = f"/tmp/skull_audio_{int(time.time())}.wav"
            audio_segment.export(temp_file, format="wav")

            pygame.mixer.music.load(temp_file)
            pygame.mixer.music.play()

            # Génère RMS en parallèle
            self._generate_rms_stream(audio_data, audio_segment.frame_rate)

            # Attend fin de lecture ou stop
            while pygame.mixer.music.get_busy() and not self._stop_event.is_set():
                if self._pause_event.is_set():
                    # Attend pendant la pause
                    while self._pause_event.is_set() and not self._stop_event.is_set():
                        time.sleep(0.1)

                time.sleep(0.1)

            # Nettoie fichier temporaire
            try:
                os.unlink(temp_file)
            except OSError:
                pass

        except Exception as e:
            self.logger.error(f"Erreur playback segment: {e}")

    def _segment_to_numpy(self, segment: AudioSegment) -> np.ndarray:
        """Convertit AudioSegment en array numpy."""
        # Convertit en mono si nécessaire
        if segment.channels > 1:
            segment = segment.set_channels(1)

        # Extrait raw audio data
        raw_data = segment.raw_data

        # Convertit selon le format
        if segment.sample_width == 2:
            audio_array = np.frombuffer(raw_data, dtype=np.int16)
            # Normalise [-1, 1]
            audio_array = audio_array.astype(np.float32) / 32768.0
        else:
            audio_array = np.frombuffer(raw_data, dtype=np.uint8)
            audio_array = audio_array.astype(np.float32) / 128.0 - 1.0

        return audio_array

    def _generate_rms_stream(self, audio_data: np.ndarray, sample_rate: int):
        """Génère le stream RMS en temps réel."""
        if not self.rms_callback:
            return

        frame_duration = VoiceConfig.FRAME_SIZE / 1000.0  # en secondes
        playback_start = time.time()

        for rms_data in self.jaw_processor.process_audio_frames(
            audio_data, sample_rate
        ):
            # Vérifie si on doit s'arrêter
            if self._stop_event.is_set():
                break

            # Gère la pause
            while self._pause_event.is_set() and not self._stop_event.is_set():
                time.sleep(0.1)
                playback_start += 0.1  # Ajuste timing

            # Synchronise avec la lecture audio réelle
            expected_time = playback_start + (rms_data.ts_ms - rms_data.ts_ms) / 1000.0
            current_time = time.time()

            if current_time < expected_time:
                time.sleep(expected_time - current_time)

            # Envoie RMS
            try:
                self.rms_callback(rms_data)
            except Exception as e:
                self.logger.error(f"Erreur callback RMS: {e}")
