"""
VoiceManager - 语音模块核心协调器

协调 STT、TTS、VAD、AudioIO 实现完整语音交互。
"""

import asyncio
import logging
import queue
from typing import Any, Dict, List, Optional

import numpy as np

from .sherpa_stt import SherpaOnnxSTT, DEFAULT_MODEL_NAME
from .base_tts import TTSFactory, BaseTTS
from .vad import VoiceActivityDetector
from .audio_io import AudioRecorder, AudioPlayer
from .text_utils import split_sentences

logger = logging.getLogger(__name__)


class VoiceManager:
    """
    语音交互管理器

    协调 STT + TTS + VAD + AudioIO 实现：
    - listen(): 监听用户语音 → 流式转录 → YouTube 风格实时字幕 → 返回识别文字
    - speak(): 文字 → 语音播放（支持语音打断）
    """

    def __init__(
        self,
        stt: SherpaOnnxSTT,
        tts: BaseTTS,
        vad: VoiceActivityDetector,
        recorder: AudioRecorder,
        player: AudioPlayer,
    ):
        self._stt = stt
        self._tts = tts
        self._vad = vad
        self._recorder = recorder
        self._player = player
        self._initialized = False
        self._stop_requested = False

    def request_stop(self):
        """请求停止当前操作（用于 Ctrl+C 处理）"""
        self._stop_requested = True
        if self._recorder.is_recording:
            self._recorder.stop()
        if self._player.is_playing:
            self._player.stop()

    @classmethod
    def from_config(cls, config: Dict[str, Any]) -> "VoiceManager":
        """从配置字典创建 VoiceManager"""
        # 防护：如果 Server 下发的是旧式 whisper 短名称，替换为 sherpa-onnx 默认模型
        raw_model = config.get("stt_model", DEFAULT_MODEL_NAME)
        if not raw_model.startswith("sherpa-onnx-"):
            model_name = DEFAULT_MODEL_NAME
        else:
            model_name = raw_model

        stt = SherpaOnnxSTT(
            model_name=model_name,
            model_dir=config.get("stt_model_dir", "./models"),
            language=config.get("language", "zh"),
            num_threads=config.get("stt_num_threads", 2),
        )

        tts = TTSFactory.create(
            "edge_tts",
            voice=config.get("tts_voice", "zh-CN-XiaoxiaoNeural"),
            rate=config.get("tts_rate", "+8%"),
            pitch=config.get("tts_pitch", "+2Hz"),
            volume=config.get("tts_volume", "+0%"),
        )

        vad = VoiceActivityDetector(
            aggressiveness=config.get("vad_aggressiveness", 2),
            sample_rate=16000,
            silence_duration_ms=config.get("silence_duration_ms", 1500),
        )

        recorder = AudioRecorder(sample_rate=16000, chunk_ms=30)
        player = AudioPlayer()

        return cls(stt=stt, tts=tts, vad=vad, recorder=recorder, player=player)

    async def initialize(self) -> None:
        """初始化所有组件"""
        print("[语音] 正在初始化语音模块...")

        print("[语音] 加载语音识别模型...", flush=True)
        await self._stt.initialize()

        await self._tts.initialize()

        self._initialized = True
        print("[语音] 模块初始化完成")

    async def listen(self) -> Optional[str]:
        """
        监听用户语音 → 流式转录 → YouTube 风格实时字幕 → 返回识别文字

        流程:
        1. 等待 VAD 确认用户在说话
        2. 边录边转录，每帧更新字幕（YouTube 风格逐字更新）
        3. sherpa-onnx 端点检测 或 webrtcvad 检测到说完 → 返回最终文字
        """
        if not self._initialized:
            raise RuntimeError("VoiceManager not initialized")

        self._stop_requested = False
        self._vad.reset()

        stream = self._stt.create_stream()
        audio_queue: queue.Queue = queue.Queue()
        speech_started = False
        pre_speech_buffer: List[np.ndarray] = []
        last_text = ""

        PRE_SPEECH_FRAMES = 15  # 保留说话前 ~450ms

        def on_audio(audio_float: np.ndarray, pcm_bytes: bytes):
            """录音回调（在音频线程中执行）"""
            if self._stop_requested:
                return
            self._vad.is_speech(pcm_bytes)
            # 始终将音频放入队列（由主循环决定是否送入 sherpa-onnx）
            audio_queue.put((audio_float.copy(), self._vad.has_speech))

        self._recorder.start(on_audio)
        print("  [等待说话...]", end="", flush=True)

        try:
            while True:
                await asyncio.sleep(0.03)  # 30ms 轮询

                if self._stop_requested:
                    return None

                # 从队列取出所有待处理音频
                frames = []
                while not audio_queue.empty():
                    try:
                        frames.append(audio_queue.get_nowait())
                    except queue.Empty:
                        break

                if not frames:
                    continue

                for frame, is_speech in frames:
                    if not speech_started:
                        if is_speech:
                            # 语音开始！送入预缓冲 + 当前帧
                            speech_started = True
                            for pre_frame in pre_speech_buffer:
                                self._stt.feed_audio(stream, pre_frame)
                            pre_speech_buffer.clear()
                            self._stt.feed_audio(stream, frame)
                            print("\r  [正在听...]                           ", end="", flush=True)
                        else:
                            # 静音期，维护预缓冲
                            pre_speech_buffer.append(frame)
                            if len(pre_speech_buffer) > PRE_SPEECH_FRAMES:
                                pre_speech_buffer.pop(0)
                    else:
                        # 说话中，所有帧都送入
                        self._stt.feed_audio(stream, frame)

                if not speech_started:
                    continue

                # 解码 + 获取实时结果（在主线程中顺序执行，线程安全）
                self._stt.decode(stream)
                text = self._stt.get_result(stream).strip()

                # YouTube 风格实时字幕更新
                if text and text != last_text:
                    last_text = text
                    display = text[:60] + "..." if len(text) > 60 else text
                    print(f"\r  [听到] {display}                    ", end="", flush=True)

                # 检测是否说完（双重检测：sherpa-onnx 端点 + webrtcvad）
                if self._stt.is_endpoint(stream) or self._vad.is_speech_finished():
                    final_text = self._stt.get_result(stream).strip()

                    if final_text:
                        # 有有效文字，结束监听
                        self._stt.reset(stream)
                        print(f"\r你: {final_text}                              ")
                        return final_text

                    # 噪音误触发，无有效文字 → 重置状态，继续监听
                    self._stt.reset(stream)
                    stream = self._stt.create_stream()
                    self._vad.reset()
                    speech_started = False
                    pre_speech_buffer.clear()
                    last_text = ""
                    print("\r  [等待说话...]", end="", flush=True)

        except asyncio.CancelledError:
            return None

        finally:
            self._recorder.stop()

    async def speak(self, text: str) -> bool:
        """
        文字 → 语音播放（分句流水线模式）

        短文本（单句）走快速路径，长文本分句后并行合成+播放，
        大幅降低首句延迟。

        支持麦克风打断检测：使用严格的 VAD 参数 + RMS 能量门限，
        避免环境噪音/开麦瞬态误触发打断。

        Returns:
            是否播放完成（False = 被打断）
        """
        if not self._initialized:
            raise RuntimeError("VoiceManager not initialized")

        if not text.strip():
            return True

        sentences = split_sentences(text)
        if not sentences:
            return True

        # 单句走快速路径（保持原逻辑，避免队列开销）
        if len(sentences) == 1:
            return await self._speak_single(sentences[0])

        # 多句走流水线
        return await self._speak_pipeline(sentences)

    async def _speak_single(self, text: str) -> bool:
        """单句快速路径（原 speak 逻辑）"""
        try:
            result = await self._tts.synthesize(text)

            interrupt_recorder = self._create_interrupt_recorder()

            await asyncio.sleep(0.2)
            interrupt_recorder.start(self._create_interrupt_callback())

            try:
                completed = await self._player.play_mp3(result.audio_data)
                if not completed:
                    print("  [被打断]")
                return completed
            finally:
                interrupt_recorder.stop()

        except Exception as e:
            logger.error(f"TTS/playback error: {e}")
            print(f"  [语音播放失败: {e}]")
            return True

    async def _speak_pipeline(self, sentences: list) -> bool:
        """
        多句流水线：生产者合成 + 消费者播放并行执行

        第一句合成完即开始播放，同时合成后续句子。
        """
        _SENTINEL = object()
        audio_queue: asyncio.Queue = asyncio.Queue(maxsize=3)

        async def producer():
            """逐句合成，放入队列"""
            try:
                for sentence in sentences:
                    if self._player._stop_event.is_set():
                        break
                    result = await self._tts.synthesize(sentence)
                    if self._player._stop_event.is_set():
                        break
                    await audio_queue.put(result.audio_data)
            except asyncio.CancelledError:
                pass
            except Exception as e:
                logger.error(f"TTS pipeline producer error: {e}")
            finally:
                # 用 put_nowait 防止队列满时阻塞（消费者可能已退出）
                try:
                    audio_queue.put_nowait(_SENTINEL)
                except asyncio.QueueFull:
                    pass

        try:
            interrupt_recorder = self._create_interrupt_recorder()

            await asyncio.sleep(0.2)
            interrupt_recorder.start(self._create_interrupt_callback())

            producer_task = asyncio.create_task(producer())

            try:
                completed = await self._player.play_mp3_queue(audio_queue, _SENTINEL)
                if not completed:
                    print("  [被打断]")
                    producer_task.cancel()
                    try:
                        await producer_task
                    except asyncio.CancelledError:
                        pass
                return completed
            finally:
                interrupt_recorder.stop()
                if not producer_task.done():
                    producer_task.cancel()
                    try:
                        await producer_task
                    except asyncio.CancelledError:
                        pass

        except Exception as e:
            logger.error(f"TTS pipeline error: {e}")
            print(f"  [语音播放失败: {e}]")
            return True

    def _create_interrupt_recorder(self) -> AudioRecorder:
        """创建打断检测录音器"""
        return AudioRecorder(sample_rate=16000, chunk_ms=30)

    def _create_interrupt_callback(self):
        """创建打断检测回调"""
        interrupt_vad = VoiceActivityDetector(
            aggressiveness=3,
            sample_rate=16000,
            silence_duration_ms=500,
            min_speech_ms=400,
            speech_confirm_frames=8,
        )

        RMS_THRESHOLD = 1500
        consecutive_loud_frames = 0
        REQUIRED_LOUD_FRAMES = 5

        def on_interrupt_audio(audio_float: np.ndarray, pcm_bytes: bytes):
            nonlocal consecutive_loud_frames

            rms = float(np.sqrt(np.mean(audio_float ** 2)) * 32767)
            if rms < RMS_THRESHOLD:
                consecutive_loud_frames = 0
                return

            consecutive_loud_frames += 1

            interrupt_vad.is_speech(pcm_bytes)
            if interrupt_vad.has_speech and consecutive_loud_frames >= REQUIRED_LOUD_FRAMES:
                self._player.stop()

        return on_interrupt_audio

    async def shutdown(self) -> None:
        """释放所有资源"""
        self._stop_requested = True

        if self._recorder.is_recording:
            self._recorder.stop()

        if self._player.is_playing:
            self._player.stop()

        await self._stt.shutdown()
        await self._tts.shutdown()

        self._initialized = False
        logger.info("VoiceManager shutdown")
