"""T-02: TTS engine layer tests."""
import threading
import time
from unittest.mock import MagicMock, patch

import pytest

from horn.tts.base import TTSEngine, TTSTimeoutError, TTSUnavailableError


class TestTTSEngineInit:
    def test_engine_can_be_created(self):
        with patch("horn.tts.base.pyttsx3") as mock_pyttsx3:
            mock_pyttsx3.init.return_value = MagicMock()
            engine = TTSEngine()
            assert engine is not None

    def test_engine_raises_unavailable_when_pyttsx3_init_fails(self):
        with patch("horn.tts.base.pyttsx3") as mock_pyttsx3:
            mock_pyttsx3.init.side_effect = Exception("SAPI not available")
            with pytest.raises(TTSUnavailableError):
                TTSEngine()


class TestTTSEngineSpeak:
    def _make_engine(self, mock_pyttsx3):
        mock_engine = MagicMock()
        mock_pyttsx3.init.return_value = mock_engine
        return TTSEngine(), mock_engine

    def test_speak_calls_tts_engine_with_text(self):
        with patch("horn.tts.base.pyttsx3") as mock_pyttsx3:
            engine, mock_inner = self._make_engine(mock_pyttsx3)
            engine.speak("老板，任务已完成")
            mock_inner.say.assert_called_once_with("老板，任务已完成")

    def test_speak_calls_run_and_wait(self):
        with patch("horn.tts.base.pyttsx3") as mock_pyttsx3:
            engine, mock_inner = self._make_engine(mock_pyttsx3)
            engine.speak("test")
            mock_inner.runAndWait.assert_called_once()

    def test_speak_raises_unavailable_on_engine_error(self):
        with patch("horn.tts.base.pyttsx3") as mock_pyttsx3:
            engine, mock_inner = self._make_engine(mock_pyttsx3)
            mock_inner.say.side_effect = Exception("SAPI error")
            with pytest.raises(TTSUnavailableError):
                engine.speak("test")

    def test_speak_raises_timeout_when_tts_exceeds_limit(self):
        with patch("horn.tts.base.pyttsx3") as mock_pyttsx3:
            engine, mock_inner = self._make_engine(mock_pyttsx3)

            def slow_run_and_wait():
                time.sleep(10)

            mock_inner.runAndWait.side_effect = slow_run_and_wait
            with pytest.raises(TTSTimeoutError):
                engine.speak("test", timeout=0.1)

    def test_speak_stops_engine_on_timeout(self):
        with patch("horn.tts.base.pyttsx3") as mock_pyttsx3:
            engine, mock_inner = self._make_engine(mock_pyttsx3)

            def slow_run_and_wait():
                time.sleep(10)

            mock_inner.runAndWait.side_effect = slow_run_and_wait
            with pytest.raises(TTSTimeoutError):
                engine.speak("test", timeout=0.1)
            mock_inner.stop.assert_called()
