"""CosyVoice TTS 适配器单元测试."""

from __future__ import annotations

from unittest.mock import MagicMock, patch

import pytest

from packages.domain.tts_config import TtsConfig


class TestTtsConfig:
    """TTS 配置解析测试."""

    def test_parse_full_config(self):
        """完整配置解析."""
        cfg = TtsConfig.parse(
            {
                "enabled": True,
                "voice_id": "longxiaochun_v3",
                "text": "你好世界",
                "speed": 1.2,
                "pitch": 2.0,
                "volume": 0.7,
                "align_mode": "full",
                "overlap_mode": "mix",
            }
        )
        assert cfg.enabled is True
        assert cfg.voice_id == "longxiaochun_v3"
        assert cfg.text == "你好世界"
        assert cfg.speed == 1.2
        assert cfg.pitch == 2.0
        assert cfg.volume == 0.7
        assert cfg.align_mode == "full"
        assert cfg.overlap_mode == "mix"

    def test_parse_disabled(self):
        """禁用状态."""
        cfg = TtsConfig.parse({"enabled": False})
        assert cfg.enabled is False

    def test_parse_none(self):
        """空配置."""
        cfg = TtsConfig.parse(None)
        assert cfg.enabled is False

    def test_parse_empty_text_still_enabled(self):
        """有 enabled 但无 text，配置仍然有效（调用方判断是否有文本）."""
        cfg = TtsConfig.parse({"enabled": True, "voice_id": "test"})
        assert cfg.enabled is True
        assert cfg.text == ""

    def test_speed_clamp(self):
        """语速边界钳制."""
        cfg = TtsConfig.parse({"enabled": True, "speed": 3.0})
        assert cfg.speed == 2.0

        cfg2 = TtsConfig.parse({"enabled": True, "speed": 0.1})
        assert cfg2.speed == 0.5

    def test_volume_clamp(self):
        """音量边界钳制."""
        cfg = TtsConfig.parse({"enabled": True, "volume": 2.0})
        assert cfg.volume == 1.0

    def test_invalid_align_mode(self):
        """无效对齐模式回退到默认."""
        cfg = TtsConfig.parse({"enabled": True, "align_mode": "invalid"})
        assert cfg.align_mode == "full"


class TestCosyVoiceTtsAdapter:
    """CosyVoice TTS 适配器测试."""

    def test_import_ok(self):
        """适配器能正常导入."""
        from packages.adapters.tts.cosyvoice_tts_service import CosyVoiceTtsService

        assert CosyVoiceTtsService is not None

    def test_provider_name(self):
        """provider_name 属性."""
        from packages.adapters.tts.cosyvoice_tts_service import CosyVoiceTtsService

        # 用 mock 替换底层 CosyVoiceService
        with patch("packages.application.cosyvoice_service.CosyVoiceService"):
            svc = CosyVoiceTtsService()
            assert svc.provider_name == "cosyvoice"

    def test_available_voices(self):
        """可用音色列表来自预设音色."""
        from packages.adapters.tts.cosyvoice_tts_service import CosyVoiceTtsService

        with patch("packages.application.cosyvoice_service.CosyVoiceService"):
            svc = CosyVoiceTtsService()
            voices = svc.available_voices()
            assert len(voices) > 0
            assert "longxiaochun_v3" in voices
            assert "longxiaoxia_v3" in voices

    def test_estimate_duration(self):
        """时长估算."""
        from packages.adapters.tts.cosyvoice_tts_service import CosyVoiceTtsService

        with patch("packages.application.cosyvoice_service.CosyVoiceService"):
            svc = CosyVoiceTtsService()
            d = svc.estimate_duration("你好世界", speed=1.0)
            assert d > 0
            # 4 个字，4 字/秒 = 1 秒
            assert abs(d - 1.0) < 0.1

    def test_estimate_duration_speed(self):
        """语速影响时长估算."""
        from packages.adapters.tts.cosyvoice_tts_service import CosyVoiceTtsService

        with patch("packages.application.cosyvoice_service.CosyVoiceService"):
            svc = CosyVoiceTtsService()
            d_normal = svc.estimate_duration("你好世界", speed=1.0)
            d_fast = svc.estimate_duration("你好世界", speed=2.0)
            assert d_fast < d_normal
            assert abs(d_fast - d_normal / 2.0) < 0.01

    def test_synthesize_empty_text_raises(self):
        """空文本抛出异常."""
        from packages.adapters.tts.cosyvoice_tts_service import CosyVoiceTtsService
        from packages.ports.tts_service import TtsError

        with patch("packages.application.cosyvoice_service.CosyVoiceService"):
            svc = CosyVoiceTtsService()
            with pytest.raises(TtsError, match="文本不能为空"):
                svc.synthesize("  ")


class TestTtsServiceFactory:
    """TTS 服务工厂测试."""

    def test_mock_provider(self):
        """mock provider 正常."""
        from apps.worker.services.tts_service_factory import get_tts_service

        svc = get_tts_service("mock")
        assert svc.provider_name == "mock"

    def test_cosyvoice_provider(self):
        """cosyvoice provider 注册正常."""
        from apps.worker.services.tts_service_factory import get_tts_service

        with patch("packages.application.cosyvoice_service.CosyVoiceService"):
            svc = get_tts_service("cosyvoice")
            assert svc.provider_name == "cosyvoice"

    def test_aliyun_alias(self):
        """aliyun 别名映射到 cosyvoice."""
        from apps.worker.services.tts_service_factory import get_tts_service

        with patch("packages.application.cosyvoice_service.CosyVoiceService"):
            svc = get_tts_service("aliyun")
            assert svc.provider_name == "cosyvoice"

    def test_dashscope_alias(self):
        """dashscope 别名映射到 cosyvoice."""
        from apps.worker.services.tts_service_factory import get_tts_service

        with patch("packages.application.cosyvoice_service.CosyVoiceService"):
            svc = get_tts_service("dashscope")
            assert svc.provider_name == "cosyvoice"

    def test_unknown_fallback_to_mock(self):
        """未知 provider 回退到 mock."""
        from apps.worker.services.tts_service_factory import get_tts_service

        svc = get_tts_service("unknown_provider")
        assert svc.provider_name == "mock"
