"""Test TTS error handling with detailed endpoint reporting."""

import asyncio
import pytest
from unittest.mock import Mock, AsyncMock, patch, MagicMock
import httpx
from voice_mode.simple_failover import simple_tts_failover


class TestTTSErrorHandling:
    """Test suite for TTS error handling with detailed error reporting."""

    @pytest.mark.asyncio
    async def test_connection_refused_all_endpoints(self):
        """Test when all TTS endpoints refuse connection."""
        with patch('voice_mode.simple_failover.TTS_BASE_URLS', ['http://localhost:8880/v1', 'https://api.openai.com/v1']), \
             patch('voice_mode.simple_failover.OPENAI_API_KEY', 'test-key'), \
             patch('voice_mode.core.text_to_speech') as mock_tts:

            # Simulate connection refused for both endpoints
            mock_tts.side_effect = [
                Exception("Connection refused"),
                Exception("Connection error")
            ]

            success, metrics, config = await simple_tts_failover(
                text="Hello world",
                voice="nova",
                model="tts-1"
            )

            assert success is False
            assert config['error_type'] == 'all_providers_failed'
            assert len(config['attempted_endpoints']) == 2
            assert config['attempted_endpoints'][0]['endpoint'] == 'http://localhost:8880/v1/audio/speech'
            assert 'Connection refused' in config['attempted_endpoints'][0]['error']
            assert config['attempted_endpoints'][1]['endpoint'] == 'https://api.openai.com/v1/audio/speech'
            assert 'Connection error' in config['attempted_endpoints'][1]['error']

    @pytest.mark.asyncio
    async def test_authentication_error_openai(self):
        """Test authentication error on OpenAI endpoint."""
        with patch('voice_mode.simple_failover.TTS_BASE_URLS', ['https://api.openai.com/v1']), \
             patch('voice_mode.simple_failover.OPENAI_API_KEY', 'invalid-key'), \
             patch('voice_mode.core.text_to_speech') as mock_tts:

            # Simulate auth error
            mock_tts.side_effect = Exception("Incorrect API key provided")

            success, metrics, config = await simple_tts_failover(
                text="Hello world",
                voice="nova",
                model="tts-1"
            )

            assert success is False
            assert config['error_type'] == 'all_providers_failed'
            assert len(config['attempted_endpoints']) == 1
            assert 'Incorrect API key' in config['attempted_endpoints'][0]['error']

    @pytest.mark.asyncio
    @pytest.mark.skip(reason="Test needs refactoring - local services may be available that don't require API key")
    async def test_no_api_key_error(self):
        """Test missing API key for cloud endpoint."""
        with patch('voice_mode.simple_failover.TTS_BASE_URLS', ['https://api.openai.com/v1']), \
             patch('voice_mode.simple_failover.OPENAI_API_KEY', None):

            success, metrics, config = await simple_tts_failover(
                text="Hello world",
                voice="nova",
                model="tts-1"
            )

            assert success is False
            # We don't even attempt the endpoint without API key
            assert config is not None

    @pytest.mark.asyncio
    async def test_successful_tts(self):
        """Test successful TTS with provider info."""
        with patch('voice_mode.simple_failover.TTS_BASE_URLS', ['http://localhost:8880/v1']), \
             patch('voice_mode.simple_failover.OPENAI_API_KEY', None), \
             patch('voice_mode.core.text_to_speech') as mock_tts:

            # Simulate successful TTS
            mock_tts.return_value = (True, {'duration': 1.5})

            success, metrics, config = await simple_tts_failover(
                text="Hello world",
                voice="af_sky",
                model="tts-1"
            )

            assert success is True
            assert metrics == {'duration': 1.5}
            assert config['provider'] == 'kokoro'
            assert config['endpoint'] == 'http://localhost:8880/v1/audio/speech'
            assert config['voice'] == 'af_sky'

    @pytest.mark.asyncio
    async def test_fallback_to_openai(self):
        """Test falling back from local to OpenAI."""
        with patch('voice_mode.simple_failover.TTS_BASE_URLS', ['http://localhost:8880/v1', 'https://api.openai.com/v1']), \
             patch('voice_mode.simple_failover.OPENAI_API_KEY', 'valid-key'), \
             patch('voice_mode.core.text_to_speech') as mock_tts:

            # First fails, second succeeds
            mock_tts.side_effect = [
                Exception("Service unavailable"),
                (True, {'duration': 2.0})
            ]

            success, metrics, config = await simple_tts_failover(
                text="Hello world",
                voice="nova",
                model="tts-1"
            )

            assert success is True
            assert config['provider'] == 'openai'
            assert config['base_url'] == 'https://api.openai.com/v1'

    @pytest.mark.asyncio
    async def test_voice_mapping(self):
        """Test that voices are mapped correctly for different providers."""
        with patch('voice_mode.simple_failover.TTS_BASE_URLS', ['https://api.openai.com/v1']), \
             patch('voice_mode.simple_failover.OPENAI_API_KEY', 'test-key'), \
             patch('voice_mode.core.text_to_speech') as mock_tts:

            # Simulate successful TTS
            mock_tts.return_value = (True, {})

            # Try with a Kokoro voice that should be mapped to OpenAI
            success, metrics, config = await simple_tts_failover(
                text="Test",
                voice="af_sky",  # Kokoro voice
                model="tts-1"
            )

            # Check that af_sky was mapped to nova for OpenAI
            mock_tts.assert_called_once()
            call_args = mock_tts.call_args
            assert call_args[1]['tts_voice'] == 'nova'  # af_sky maps to nova

    @pytest.mark.asyncio
    async def test_detailed_error_info(self):
        """Test that detailed error info is included for each endpoint."""
        with patch('voice_mode.simple_failover.TTS_BASE_URLS', ['http://localhost:8880/v1', 'https://api.openai.com/v1']), \
             patch('voice_mode.simple_failover.OPENAI_API_KEY', 'test-key'), \
             patch('voice_mode.core.text_to_speech') as mock_tts:

            # Different errors for each endpoint
            mock_tts.side_effect = [
                Exception("404 Not Found"),
                Exception("Rate limit exceeded")
            ]

            success, metrics, config = await simple_tts_failover(
                text="Hello",
                voice="nova",
                model="tts-1-hd"
            )

            assert success is False
            assert config['error_type'] == 'all_providers_failed'

            # Check first endpoint error details
            endpoint1 = config['attempted_endpoints'][0]
            assert endpoint1['provider'] == 'kokoro'
            assert endpoint1['voice'] == 'nova'
            assert endpoint1['model'] == 'tts-1-hd'
            assert '404 Not Found' in endpoint1['error']

            # Check second endpoint error details
            endpoint2 = config['attempted_endpoints'][1]
            assert endpoint2['provider'] == 'openai'
            assert endpoint2['voice'] == 'nova'
            assert 'Rate limit' in endpoint2['error']

    @pytest.mark.asyncio
    async def test_ref_text_override_on_non_clone_voice_does_not_raise(self):
        """A ref_text override targeting a non-clone voice must fall through to
        the normal TTS endpoints, not raise UnboundLocalError (VM-1439).

        Before the fix, the ``elif ref_text_override is not None`` branch
        logged a warning but never assigned ``endpoints_to_try``, so the
        for-loop hit ``local variable 'endpoints_to_try' referenced before
        assignment``.
        """
        with patch('voice_mode.simple_failover.TTS_BASE_URLS', ['http://localhost:8880/v1']), \
             patch('voice_mode.simple_failover.OPENAI_API_KEY', None), \
             patch('voice_mode.voice_profiles.is_clone_voice', return_value=False), \
             patch('voice_mode.core.text_to_speech') as mock_tts:

            mock_tts.return_value = (True, {'duration': 1.0})

            success, metrics, config = await simple_tts_failover(
                text="Hello world",
                voice="nova",  # not a clone voice
                model="tts-1",
                ref_text="this override is ignored for a non-clone voice",
            )

            assert success is True
            assert config['endpoint'] == 'http://localhost:8880/v1/audio/speech'
            mock_tts.assert_called_once()
            # The ref_text override is popped before forwarding to TTS.
            assert 'ref_text' not in mock_tts.call_args[1]