"""Capture audio via ALSA pour ReSpeaker 2-Mic Hat."""

import logging
import queue
import threading
import time
from typing import Optional, Callable, Generator

import sounddevice as sd
import numpy as np

from .config import AudioConfig


logger = logging.getLogger(__name__)


class AudioCaptureError(Exception):
    """Exception pour erreurs de capture audio."""

    pass


class AudioCapture:
    """Capture audio en temps réel depuis le micro ReSpeaker."""

    def __init__(self, config: AudioConfig):
        """
        Initialise la capture audio.

        Args:
            config: Configuration audio
        """
        self.config = config
        self.audio_queue: queue.Queue[bytes] = queue.Queue(maxsize=50)
        self.stream: Optional[sd.InputStream] = None
        self.running = False
        self.thread: Optional[threading.Thread] = None

        # Vérification device
        self._check_audio_device()

    def _check_audio_device(self) -> None:
        """Vérifie que le device audio est disponible."""
        try:
            devices = sd.query_devices()
            logger.info(f"Devices audio disponibles: {len(devices)}")

            # Recherche ReSpeaker ou device par défaut
            input_device = None
            for i, device in enumerate(devices):
                device_name = device["name"].lower()
                if any(
                    keyword in device_name for keyword in ["respeaker", "usb", "mic"]
                ):
                    if device["max_input_channels"] > 0:
                        input_device = i
                        logger.info(f"Device trouvé: {device['name']} (index {i})")
                        break

            if input_device is None:
                # Utiliser device par défaut
                default_device = sd.query_devices(kind="input")
                logger.warning(
                    f"ReSpeaker non trouvé, utilisation device par défaut: {default_device['name']}"
                )

        except Exception as e:
            logger.error(f"Erreur vérification audio: {e}")
            raise AudioCaptureError(f"Impossible de vérifier les devices audio: {e}")

    def _audio_callback(
        self,
        indata: np.ndarray,
        frames: int,
        time_info: sd.CallbackFlags,
        status: sd.CallbackFlags,
    ) -> None:
        """Callback audio appelé par sounddevice."""
        if status:
            logger.warning(f"Status audio callback: {status}")

        try:
            # Conversion mono si nécessaire
            if indata.shape[1] > 1:
                audio_mono = np.mean(indata, axis=1)
            else:
                audio_mono = indata[:, 0]

            # Conversion float32 -> int16
            audio_int16 = (audio_mono * 32767).astype(np.int16)

            # Mise en queue non-bloquante
            try:
                self.audio_queue.put_nowait(audio_int16.tobytes())
            except queue.Full:
                logger.warning("Queue audio pleine, frame perdue")

        except Exception as e:
            logger.error(f"Erreur callback audio: {e}")

    def start(self) -> None:
        """Démarre la capture audio."""
        if self.running:
            logger.warning("Capture déjà démarrée")
            return

        try:
            self.stream = sd.InputStream(
                callback=self._audio_callback,
                samplerate=self.config.sample_rate,
                channels=self.config.channels,
                blocksize=self.config.frame_size,
                dtype=np.float32,
                latency="low",
            )

            self.stream.start()
            self.running = True
            logger.info(
                f"Capture audio démarrée: {self.config.sample_rate}Hz, {self.config.channels}ch"
            )

        except Exception as e:
            logger.error(f"Erreur démarrage capture: {e}")
            raise AudioCaptureError(f"Impossible de démarrer la capture: {e}")

    def stop(self) -> None:
        """Arrête la capture audio."""
        self.running = False

        if self.stream:
            try:
                self.stream.stop()
                self.stream.close()
                logger.info("Capture audio arrêtée")
            except Exception as e:
                logger.error(f"Erreur arrêt capture: {e}")

        # Vider la queue
        while not self.audio_queue.empty():
            try:
                self.audio_queue.get_nowait()
            except queue.Empty:
                break

    def read_frame(self, timeout: float = 0.1) -> Optional[bytes]:
        """
        Lit une frame audio depuis la queue.

        Args:
            timeout: Timeout en secondes

        Returns:
            Frame audio en bytes ou None si timeout
        """
        if not self.running:
            return None

        try:
            return self.audio_queue.get(timeout=timeout)
        except queue.Empty:
            return None

    def get_frames(self) -> Generator[bytes, None, None]:
        """Générateur de frames audio."""
        while self.running:
            frame = self.read_frame()
            if frame is not None:
                yield frame
            else:
                time.sleep(0.001)  # Éviter CPU à 100%

    def get_queue_size(self) -> int:
        """Retourne la taille actuelle de la queue."""
        return self.audio_queue.qsize()

    def __enter__(self):
        """Context manager."""
        self.start()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Context manager."""
        self.stop()


def list_audio_devices() -> None:
    """Utilitaire pour lister les devices audio disponibles."""
    devices = sd.query_devices()
    print("Devices audio disponibles:")
    for i, device in enumerate(devices):
        device_type = []
        if device["max_input_channels"] > 0:
            device_type.append("IN")
        if device["max_output_channels"] > 0:
            device_type.append("OUT")

        print(
            f"  {i}: {device['name']} ({'/'.join(device_type)}) - {device['default_samplerate']}Hz"
        )


def create_audio_capture(
    sample_rate: int = 16000, frame_size: int = 320
) -> AudioCapture:
    """Fonction utilitaire pour créer une capture audio."""
    config = AudioConfig(sample_rate=sample_rate, channels=1, frame_size=frame_size)
    return AudioCapture(config)
