from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from app.services.tts import TTSService


class TestTTSService:
    """Test Text-to-Speech service functionality"""

    @pytest.fixture
    def tts_service_google(self):
        """Create TTS service with mocked Google TTS client"""
        with patch('app.services.tts.settings') as mock_settings:
            mock_settings.google_tts_credentials = "/path/to/credentials.json"
            mock_settings.elevenlabs_api_key = ""
            mock_settings.google_tts_voices = {
                "en-US": "en-US-Neural2-D",
                "es-ES": "es-ES-Neural2-A"
            }
            
            with patch('app.services.tts.texttospeech.TextToSpeechClient') as mock_client:
                service = TTSService()
                service.google_client = MagicMock()
                return service

    @pytest.fixture
    def tts_service_elevenlabs(self):
        """Create TTS service with mocked ElevenLabs"""
        with patch('app.services.tts.settings') as mock_settings:
            mock_settings.google_tts_credentials = ""
            mock_settings.elevenlabs_api_key = "test_api_key"
            mock_settings.elevenlabs_voices = {
                "en-US": "21m00Tcm4TlvDq8ikWAM",
                "es-ES": "VR6AewLTigWG4xSOukaG"
            }
            
            service = TTSService()
            service.google_client = None
            return service

    @pytest.fixture
    def sample_ad_vtt(self):
        """Sample audio description VTT for testing"""
        return """WEBVTT

00:00:01.000 --> 00:00:03.000
[Upbeat music plays]

00:00:05.000 --> 00:00:07.000
[Person walks into frame]

00:00:10.000 --> 00:00:12.000
[Graph appears on screen]
"""

    @pytest.mark.asyncio
    async def test_synthesize_with_google_success(self, tts_service_google, sample_ad_vtt):
        """Test successful TTS synthesis with Google"""
        # Mock Google TTS response
        mock_response = MagicMock()
        mock_response.audio_content = b"fake_audio_data"
        tts_service_google.google_client.synthesize_speech.return_value = mock_response
        
        # Mock AudioSegment operations
        with patch('app.services.tts.AudioSegment') as mock_audio_segment:
            mock_segment = MagicMock()
            mock_audio_segment.from_file.return_value = mock_segment
            mock_audio_segment.silent.return_value = mock_segment
            mock_audio_segment.empty.return_value = mock_segment
            
            # Mock the sum operation for combining segments
            mock_combined = MagicMock()
            mock_combined.export.return_value = None
            
            with patch('builtins.sum', return_value=mock_combined):
                # Mock BytesIO for output
                with patch('app.services.tts.io.BytesIO') as mock_bytesio:
                    mock_buffer = MagicMock()
                    mock_buffer.getvalue.return_value = b"final_audio_data"
                    mock_bytesio.return_value = mock_buffer
                    
                    result = await tts_service_google._synthesize_with_google(
                        sample_ad_vtt, 
                        "en-US"
                    )
                    
                    assert result == b"final_audio_data"

    @pytest.mark.asyncio
    async def test_synthesize_with_elevenlabs_success(self, tts_service_elevenlabs, sample_ad_vtt):
        """Test successful TTS synthesis with ElevenLabs"""
        # Mock ElevenLabs API response
        mock_audio_data = b"elevenlabs_audio_data"
        
        with patch.object(tts_service_elevenlabs, '_synthesize_text_elevenlabs') as mock_elevenlabs:
            mock_elevenlabs.return_value = mock_audio_data
            
            # Mock AudioSegment operations
            with patch('app.services.tts.AudioSegment') as mock_audio_segment:
                mock_segment = MagicMock()
                mock_audio_segment.from_file.return_value = mock_segment
                mock_audio_segment.silent.return_value = mock_segment
                mock_audio_segment.empty.return_value = mock_segment
                
                mock_combined = MagicMock()
                mock_combined.export.return_value = None
                
                with patch('builtins.sum', return_value=mock_combined):
                    with patch('app.services.tts.io.BytesIO') as mock_bytesio:
                        mock_buffer = MagicMock()
                        mock_buffer.getvalue.return_value = b"final_elevenlabs_audio"
                        mock_bytesio.return_value = mock_buffer
                        
                        result = await tts_service_elevenlabs._synthesize_with_elevenlabs(
                            sample_ad_vtt,
                            "en-US"
                        )
                        
                        assert result == b"final_elevenlabs_audio"

    @pytest.mark.asyncio
    async def test_synthesize_no_service_configured(self):
        """Test error when no TTS service is configured"""
        with patch('app.services.tts.settings') as mock_settings:
            mock_settings.google_tts_credentials = ""
            mock_settings.elevenlabs_api_key = ""
            
            service = TTSService()
            service.google_client = None
            service.elevenlabs_available = False
            
            with pytest.raises(ValueError, match="No TTS service available"):
                await service.synthesize_audio_description("WEBVTT\n", "en-US")

    @pytest.mark.asyncio
    async def test_synthesize_fallback_to_elevenlabs(self, tts_service_elevenlabs):
        """Test fallback from Google to ElevenLabs on failure"""
        # Set up service with both providers available
        tts_service_elevenlabs.google_client = MagicMock()
        tts_service_elevenlabs.elevenlabs_available = True
        
        with patch.object(tts_service_elevenlabs, '_synthesize_with_google') as mock_google:
            mock_google.side_effect = Exception("Google TTS failed")
            
            with patch.object(tts_service_elevenlabs, '_synthesize_with_elevenlabs') as mock_elevenlabs:
                mock_elevenlabs.return_value = b"elevenlabs_fallback_audio"
                
                result = await tts_service_elevenlabs.synthesize_audio_description(
                    "WEBVTT\n\n00:00:01.000 --> 00:00:02.000\n[Test]",
                    "en-US"
                )
                
                assert result == b"elevenlabs_fallback_audio"
                mock_google.assert_called_once()
                mock_elevenlabs.assert_called_once()

    def test_parse_ad_cues(self, tts_service_google, sample_ad_vtt):
        """Test parsing audio description cues"""
        cues = tts_service_google._parse_ad_cues(sample_ad_vtt)
        
        assert len(cues) == 3
        assert cues[0]["start_time"] == 1.0
        assert cues[0]["end_time"] == 3.0
        assert cues[0]["text"] == "[Upbeat music plays]"
        assert cues[1]["start_time"] == 5.0
        assert cues[1]["end_time"] == 7.0
        assert cues[1]["text"] == "[Person walks into frame]"

    def test_parse_ad_cues_empty_vtt(self, tts_service_google):
        """Test parsing empty VTT content"""
        empty_vtt = "WEBVTT\n"
        
        cues = tts_service_google._parse_ad_cues(empty_vtt)
        
        assert len(cues) == 0

    def test_parse_timestamp(self, tts_service_google):
        """Test timestamp parsing"""
        # Test HH:MM:SS.mmm format
        assert tts_service_google._parse_timestamp("01:23:45.678") == 5025.678
        
        # Test MM:SS.mmm format  
        assert tts_service_google._parse_timestamp("23:45.678") == 1425.678
        
        # Test without milliseconds
        assert tts_service_google._parse_timestamp("01:23:45") == 5025.0

    def test_parse_timestamp_invalid_format(self, tts_service_google):
        """Test error handling for invalid timestamp"""
        with pytest.raises(ValueError, match="Invalid timestamp format"):
            tts_service_google._parse_timestamp("invalid:timestamp")

    @pytest.mark.asyncio
    async def test_synthesize_text_elevenlabs_success(self, tts_service_elevenlabs):
        """Test ElevenLabs text synthesis"""
        mock_audio_data = b"elevenlabs_audio_response"

        mock_response = AsyncMock()
        mock_response.status = 200
        mock_response.read = AsyncMock(return_value=mock_audio_data)

        # async with session.post(...) as response: needs a sync callable returning async CM
        mock_post_cm = MagicMock()
        mock_post_cm.__aenter__ = AsyncMock(return_value=mock_response)
        mock_post_cm.__aexit__ = AsyncMock(return_value=False)

        mock_session = MagicMock()
        mock_session.post = MagicMock(return_value=mock_post_cm)
        mock_session.__aenter__ = AsyncMock(return_value=mock_session)
        mock_session.__aexit__ = AsyncMock(return_value=False)

        with patch('app.services.tts.aiohttp.ClientSession', return_value=mock_session):
            result = await tts_service_elevenlabs._synthesize_text_elevenlabs(
                "Test text",
                "21m00Tcm4TlvDq8ikWAM"
            )

        assert result == mock_audio_data

    @pytest.mark.asyncio
    async def test_synthesize_text_elevenlabs_error(self, tts_service_elevenlabs):
        """Test ElevenLabs API error handling"""
        mock_response = AsyncMock()
        mock_response.status = 400
        mock_response.text = AsyncMock(return_value="Bad request error")

        mock_post_cm = MagicMock()
        mock_post_cm.__aenter__ = AsyncMock(return_value=mock_response)
        mock_post_cm.__aexit__ = AsyncMock(return_value=False)

        mock_session = MagicMock()
        mock_session.post = MagicMock(return_value=mock_post_cm)
        mock_session.__aenter__ = AsyncMock(return_value=mock_session)
        mock_session.__aexit__ = AsyncMock(return_value=False)

        with patch('app.services.tts.aiohttp.ClientSession', return_value=mock_session):
            with pytest.raises(ValueError, match="ElevenLabs TTS failed: 400"):
                await tts_service_elevenlabs._synthesize_text_elevenlabs(
                    "Test text",
                    "voice_id"
                )

    def test_get_elevenlabs_voice_default(self, tts_service_elevenlabs):
        """Test getting default ElevenLabs voice for language"""
        with patch('app.services.tts.settings') as mock_settings:
            mock_settings.elevenlabs_voices = {
                "en-US": "default_voice_id",
                "es-ES": "spanish_voice_id"
            }
            
            voice_id = tts_service_elevenlabs._get_elevenlabs_voice("en-US")
            assert voice_id == "default_voice_id"

    def test_get_elevenlabs_voice_custom(self, tts_service_elevenlabs):
        """Test getting custom ElevenLabs voice"""
        voice_id = tts_service_elevenlabs._get_elevenlabs_voice("en-US", "custom_voice")
        assert voice_id == "custom_voice"

    def test_get_elevenlabs_voice_fallback(self, tts_service_elevenlabs):
        """Test ElevenLabs voice fallback for unknown language"""
        with patch('app.services.tts.settings') as mock_settings:
            mock_settings.elevenlabs_voices = {}
            
            voice_id = tts_service_elevenlabs._get_elevenlabs_voice("unknown-LANG")
            assert voice_id == "21m00Tcm4TlvDq8ikWAM"  # Default fallback

    def test_service_initialization_no_credentials(self):
        """Test service initialization with no credentials"""
        with patch('app.services.tts.settings') as mock_settings:
            mock_settings.google_tts_credentials = ""
            mock_settings.elevenlabs_api_key = ""
            
            service = TTSService()
            
            assert service.google_client is None
            assert service.elevenlabs_available is False