"""
音频 IO - 麦克风录音 + 音频播放

使用 sounddevice 录音和播放。
"""

import asyncio
import io
import logging
import threading
from typing import Callable, Optional

import numpy as np
import sounddevice as sd

logger = logging.getLogger(__name__)


class AudioRecorder:
    """
    麦克风录音器

    使用 sounddevice InputStream 实时录音。
    """

    def __init__(self, sample_rate: int = 16000, chunk_ms: int = 30):
        """
        Args:
            sample_rate: 采样率
            chunk_ms: 每帧时长（毫秒），webrtcvad 要求 10/20/30
        """
        self._sample_rate = sample_rate
        self._chunk_ms = chunk_ms
        self._chunk_size = int(sample_rate * chunk_ms / 1000)  # 30ms = 480 samples @16kHz
        self._stream: Optional[sd.InputStream] = None
        self._callback: Optional[Callable] = None
        self._is_recording = False

    def start(self, callback: Callable[[np.ndarray, bytes], None]) -> None:
        """
        开始录音

        Args:
            callback: 每帧回调 (float32_audio, pcm16_bytes)
        """
        self._callback = callback
        self._is_recording = True

        def audio_callback(indata, frames, time_info, status):
            if status:
                logger.warning(f"Audio input status: {status}")
            if not self._is_recording:
                return

            # indata: (frames, channels) float32
            audio_float = indata[:, 0].copy()  # 取单通道

            # 转换为 PCM16 bytes 给 VAD
            audio_int16 = (audio_float * 32767).astype(np.int16)
            pcm_bytes = audio_int16.tobytes()

            if self._callback:
                self._callback(audio_float, pcm_bytes)

        self._stream = sd.InputStream(
            samplerate=self._sample_rate,
            channels=1,
            dtype="float32",
            blocksize=self._chunk_size,
            callback=audio_callback,
        )
        self._stream.start()
        logger.debug("Audio recording started")

    def stop(self) -> None:
        """停止录音"""
        self._is_recording = False
        if self._stream:
            try:
                self._stream.stop()
                self._stream.close()
            except Exception as e:
                logger.debug(f"Stop stream error: {e}")
            self._stream = None
        logger.debug("Audio recording stopped")

    @property
    def is_recording(self) -> bool:
        return self._is_recording


class AudioPlayer:
    """音频播放器，支持中途打断"""

    def __init__(self):
        self._playing = False
        self._stop_event = threading.Event()

    @property
    def is_playing(self) -> bool:
        return self._playing

    def stop(self) -> None:
        """停止播放"""
        self._stop_event.set()
        sd.stop()

    async def play_mp3(self, data: bytes) -> bool:
        """
        播放 mp3 音频数据

        使用 pydub 解码 mp3 → PCM，然后用 sounddevice 播放。
        支持通过 stop() 打断。

        Args:
            data: mp3 音频数据

        Returns:
            是否播放完成（False = 被打断）
        """
        self._stop_event.clear()
        self._playing = True

        try:
            from pydub import AudioSegment

            audio = AudioSegment.from_mp3(io.BytesIO(data))
            samples = np.array(audio.get_array_of_samples(), dtype=np.float32) / 32768.0
            if audio.channels == 2:
                samples = samples.reshape(-1, 2)

            def _play():
                sd.play(samples, samplerate=audio.frame_rate)
                # 用小间隔轮询，以便及时响应 stop
                while sd.get_stream().active:
                    if self._stop_event.wait(timeout=0.05):
                        sd.stop()
                        return False
                return True

            completed = await asyncio.get_event_loop().run_in_executor(None, _play)
            return completed
        except ImportError:
            logger.error("pydub not installed. Install with: pip install pydub")
            raise
        except Exception as e:
            logger.error(f"Audio playback error: {e}")
            raise
        finally:
            self._playing = False

    async def play_mp3_queue(self, audio_queue: "asyncio.Queue", sentinel: object) -> bool:
        """
        从队列逐句取出 MP3 bytes 播放

        生产者将合成好的 MP3 bytes 放入队列，本方法逐个取出播放。
        取到 sentinel 对象表示所有句子已入队，播放完毕后返回。

        Args:
            audio_queue: asyncio.Queue，元素为 bytes (MP3) 或 sentinel
            sentinel: 结束标记对象

        Returns:
            是否全部播放完成（False = 被打断）
        """
        self._stop_event.clear()
        self._playing = True

        try:
            from pydub import AudioSegment

            while True:
                if self._stop_event.is_set():
                    return False

                # 带超时轮询，确保能响应 stop_event（Ctrl+C 打断）
                try:
                    item = await asyncio.wait_for(audio_queue.get(), timeout=0.1)
                except asyncio.TimeoutError:
                    continue

                if item is sentinel:
                    return True

                if self._stop_event.is_set():
                    return False

                # 解码 MP3 → PCM → 播放
                audio = AudioSegment.from_mp3(io.BytesIO(item))
                samples = np.array(audio.get_array_of_samples(), dtype=np.float32) / 32768.0
                if audio.channels == 2:
                    samples = samples.reshape(-1, 2)
                frame_rate = audio.frame_rate
                stop_event = self._stop_event

                def _play_chunk():
                    sd.play(samples, samplerate=frame_rate)
                    try:
                        while sd.get_stream().active:
                            if stop_event.wait(timeout=0.05):
                                sd.stop()
                                return False
                        return True
                    finally:
                        # 显式释放流，防止下一句 sd.play() 打开设备时冲突
                        try:
                            sd.stop()
                        except Exception:
                            pass

                completed = await asyncio.get_event_loop().run_in_executor(None, _play_chunk)
                if not completed:
                    return False

        except ImportError:
            logger.error("pydub not installed. Install with: pip install pydub")
            raise
        except Exception as e:
            logger.error(f"Audio queue playback error: {e}")
            raise
        finally:
            self._playing = False
