"""Sounddevice audio capture engine (cross-platform, via PortAudio)."""

from typing import Any, Dict, List, Optional

import numpy as np

from wled_controller.core.audio.base import (
    AudioCaptureEngine,
    AudioCaptureStreamBase,
    AudioDeviceInfo,
)
from wled_controller.utils import get_logger

logger = get_logger(__name__)


class SounddeviceCaptureStream(AudioCaptureStreamBase):
    """Audio capture stream using sounddevice (PortAudio)."""

    def __init__(self, device_index: int, is_loopback: bool, config: Dict[str, Any]):
        super().__init__(device_index, is_loopback, config)
        self._sd_stream = None
        self._channels = config.get("channels", 2)
        self._sample_rate = config.get("sample_rate", 44100)
        self._chunk_size = config.get("chunk_size", 2048)

    @property
    def channels(self) -> int:
        return self._channels

    @property
    def sample_rate(self) -> int:
        return self._sample_rate

    @property
    def chunk_size(self) -> int:
        return self._chunk_size

    def initialize(self) -> None:
        if self._initialized:
            return

        try:
            import sounddevice as sd
        except ImportError:
            raise RuntimeError("sounddevice is not installed — sounddevice engine unavailable")

        # Resolve device
        device_id = self.device_index if self.device_index >= 0 else None
        if device_id is not None:
            dev_info = sd.query_devices(device_id)
            self._channels = min(self._channels, int(dev_info["max_input_channels"]))
            if self._channels < 1:
                raise RuntimeError(
                    f"Device {device_id} ({dev_info['name']}) has no input channels"
                )
            self._sample_rate = int(dev_info["default_samplerate"])

        self._sd_stream = sd.InputStream(
            device=device_id,
            channels=self._channels,
            samplerate=self._sample_rate,
            blocksize=self._chunk_size,
            dtype="float32",
        )
        self._sd_stream.start()
        self._initialized = True
        logger.info(
            f"sounddevice stream opened: device={device_id} loopback={self.is_loopback} "
            f"channels={self._channels} sr={self._sample_rate}"
        )

    def cleanup(self) -> None:
        if self._sd_stream is not None:
            try:
                self._sd_stream.stop()
                self._sd_stream.close()
            except Exception as e:
                logger.debug("Sounddevice stream cleanup: %s", e)
                pass
            self._sd_stream = None
        self._initialized = False

    def read_chunk(self) -> Optional[np.ndarray]:
        if self._sd_stream is None:
            return None
        try:
            # sd.InputStream.read() returns (data, overflowed)
            data, _ = self._sd_stream.read(self._chunk_size)
            # data shape: (chunk_size, channels) — flatten to interleaved 1-D
            return data.flatten().astype(np.float32)
        except Exception as e:
            logger.warning(f"sounddevice read error: {e}")
            return None


class SounddeviceEngine(AudioCaptureEngine):
    """Sounddevice (PortAudio) audio capture engine — cross-platform."""

    ENGINE_TYPE = "sounddevice"
    ENGINE_PRIORITY = 5

    @classmethod
    def is_available(cls) -> bool:
        try:
            import sounddevice  # noqa: F401
            return True
        except ImportError as e:
            logger.debug("Sounddevice engine unavailable: %s", e)
            return False

    @classmethod
    def get_default_config(cls) -> Dict[str, Any]:
        return {
            "sample_rate": 44100,
            "chunk_size": 2048,
        }

    @classmethod
    def enumerate_devices(cls) -> List[AudioDeviceInfo]:
        try:
            import sounddevice as sd
        except ImportError as e:
            logger.debug("Cannot enumerate sounddevice devices: %s", e)
            return []

        try:
            devices = sd.query_devices()
            result = []
            for i, dev in enumerate(devices):
                max_in = int(dev["max_input_channels"])
                if max_in < 1:
                    continue

                name = dev["name"]
                # On PulseAudio/PipeWire, monitor sources are loopback-capable
                is_loopback = "monitor" in name.lower()

                result.append(AudioDeviceInfo(
                    index=i,
                    name=name,
                    is_input=True,
                    is_loopback=is_loopback,
                    channels=max_in,
                    default_samplerate=dev["default_samplerate"],
                ))

            return result

        except Exception as e:
            logger.error(f"Failed to enumerate sounddevice devices: {e}", exc_info=True)
            return []

    @classmethod
    def create_stream(
        cls,
        device_index: int,
        is_loopback: bool,
        config: Dict[str, Any],
    ) -> SounddeviceCaptureStream:
        merged = {**cls.get_default_config(), **config}
        return SounddeviceCaptureStream(device_index, is_loopback, merged)
