import io
import re

from google.cloud import texttospeech
from pydub import AudioSegment

from ..core.config import settings
from ..core.logging import get_logger
from ..lib import locales as locale_lib

logger = get_logger(__name__)


class TTSSynthesisError(Exception):
    """Raised when TTS synthesis fails after all retries."""

    def __init__(self, message: str, cue_index: int, cue_text: str, api_response_info: str = None):
        super().__init__(message)
        self.cue_index = cue_index
        self.cue_text = cue_text
        self.api_response_info = api_response_info


class GeminiTTSService:
    """Text-to-Speech service using Google Cloud Text-to-Speech API with Gemini models."""

    def __init__(self):
        self.client = texttospeech.TextToSpeechClient()
        self.model = settings.gemini_tts_model
        self.default_voice = settings.gemini_tts_default_voice
        logger.info(f"Gemini TTS service initialized with model: {self.model}")

    @staticmethod
    def _extract_retry_after(error: Exception) -> float | None:
        """Return seconds to wait from a Google API 429 retryDelay, or None."""
        msg = str(error)
        m = re.search(r"retry in ([0-9.]+)s", msg, re.IGNORECASE)
        if m:
            return float(m.group(1)) + 5
        m = re.search(r"'retryDelay':\s*'([0-9.]+)s'", msg)
        if m:
            return float(m.group(1)) + 5
        return None

    async def synthesize_text(
        self,
        text: str,
        voice_name: str,
        language: str = "en",
        model: str = "flash",
        speed: float = 1.0,
        style_prompt: str = ""
    ) -> bytes:
        """
        Synthesize text to MP3 using Google Cloud TTS with Gemini model.

        Args:
            text: The text to synthesize
            voice_name: Gemini voice name (e.g. "Kore", "Puck")
            language: Language code (e.g. "en", "en-US", "fr")
            model: Model variant key — "flash" or "pro"
            speed: Speech rate multiplier (0.25–4.0)
            style_prompt: Natural-language style instruction sent as prompt
        """
        if not text.strip():
            raise ValueError("Text cannot be empty")

        if voice_name not in settings.gemini_tts_voices:
            logger.warning(f"Unknown voice '{voice_name}', using default '{self.default_voice}'")
            voice_name = self.default_voice

        model_id = settings.gemini_tts_models.get(model, settings.gemini_tts_model)
        language_code = locale_lib.get_tts_lang(language)

        try:
            synthesis_input = texttospeech.SynthesisInput(text=text)
            if style_prompt:
                synthesis_input = texttospeech.SynthesisInput(
                    text=text,
                    prompt=style_prompt,
                )

            response = self.client.synthesize_speech(
                input=synthesis_input,
                voice=texttospeech.VoiceSelectionParams(
                    language_code=language_code,
                    name=voice_name,
                    model_name=model_id,
                ),
                audio_config=texttospeech.AudioConfig(
                    audio_encoding=texttospeech.AudioEncoding.MP3,
                    speaking_rate=speed,
                ),
            )

            if not response.audio_content:
                raise ValueError("Empty audio content in Cloud TTS response")

            return response.audio_content

        except Exception as e:
            logger.error(
                f"Gemini TTS synthesis failed: {e}. "
                f"text_len={len(text)}, voice={voice_name}, model={model_id}, lang={language_code}"
            )
            raise

    async def synthesize_preview(
        self,
        voice_name: str,
        language: str = "en",
        model: str = "flash",
        speed: float = 1.0,
        style_prompt: str = ""
    ) -> bytes:
        """Generate a preview audio sample for voice selection."""
        sample_text = (
            settings.gemini_tts_preview_samples.get(language)
            or locale_lib.get_preview_sample(language)
        )
        return await self.synthesize_text(
            sample_text,
            voice_name,
            language,
            model=model,
            speed=speed,
            style_prompt=style_prompt,
        )

    async def _synthesize_cue_with_retry(
        self,
        cue_index: int,
        text: str,
        voice_name: str,
        language: str,
        model: str,
        speed: float,
        style_prompt: str,
        max_attempts: int = 3,
        base_delay: float = 1.0
    ) -> bytes:
        """Synthesize a single cue with retry, honouring API-provided retryDelay on 429."""
        import asyncio
        import random

        last_exception = None
        api_response_info = None

        for attempt in range(max_attempts):
            try:
                return await self.synthesize_text(
                    text,
                    voice_name,
                    language,
                    model=model,
                    speed=speed,
                    style_prompt=style_prompt,
                )
            except Exception as e:
                last_exception = e
                api_response_info = str(e)

                if attempt < max_attempts - 1:
                    api_delay = self._extract_retry_after(e)
                    delay = api_delay if api_delay else base_delay * (2 ** attempt) + random.uniform(0, 1)
                    logger.warning(
                        f"TTS attempt {attempt + 1}/{max_attempts} failed for cue {cue_index}. "
                        f"Retrying in {delay:.2f}s. Error: {e}"
                    )
                    await asyncio.sleep(delay)
                else:
                    logger.error(
                        f"TTS FAILED after {max_attempts} attempts for cue {cue_index}. "
                        f"text='{text[:50]}{'...' if len(text) > 50 else ''}'. Error: {e}"
                    )

        raise TTSSynthesisError(
            message=f"TTS synthesis failed after {max_attempts} attempts: {last_exception}",
            cue_index=cue_index,
            cue_text=text,
            api_response_info=api_response_info,
        )

    async def synthesize_audio_description(
        self,
        ad_vtt_content: str,
        language: str = "en",
        voice_name: str | None = None,
        model: str = "flash",
        speed: float = 1.0,
        style_prompt: str = ""
    ) -> bytes:
        """
        Synthesize full audio description from VTT content.
        Maintains timing alignment with original VTT cues.
        """
        if voice_name is None:
            voice_name = self.default_voice

        if voice_name not in settings.gemini_tts_voices:
            logger.warning(f"Unknown voice '{voice_name}', using default '{self.default_voice}'")
            voice_name = self.default_voice

        cues = self._parse_ad_cues(ad_vtt_content)
        if not cues:
            raise ValueError("No audio description cues found in VTT content")

        logger.info(
            f"Synthesizing {len(cues)} AD cues: voice='{voice_name}', model='{model}', speed={speed}x"
        )

        audio_segments = []
        current_audio_position = 0.0

        for i, cue in enumerate(cues):
            target_start_time = cue["start_time"]

            if target_start_time > current_audio_position:
                silence_duration = target_start_time - current_audio_position
                audio_segments.append(AudioSegment.silent(duration=int(silence_duration * 1000)))
                current_audio_position = target_start_time

            text = cue["text"].strip()
            if text:
                if not text.endswith(('.', '!', '?')):
                    text += "."

                audio_data = await self._synthesize_cue_with_retry(
                    cue_index=i,
                    text=text,
                    voice_name=voice_name,
                    language=language,
                    model=model,
                    speed=speed,
                    style_prompt=style_prompt,
                    max_attempts=3,
                    base_delay=1.0,
                )

                audio_segment = AudioSegment.from_file(io.BytesIO(audio_data), format="mp3")
                audio_segments.append(audio_segment)
                current_audio_position += len(audio_segment) / 1000.0

        final_audio = sum(audio_segments, AudioSegment.empty()) if audio_segments else AudioSegment.silent(duration=1000)
        output_buffer = io.BytesIO()
        final_audio.export(output_buffer, format="mp3", bitrate="128k")

        logger.info(f"Audio description synthesized: {len(output_buffer.getvalue())} bytes")
        return output_buffer.getvalue()

    def _parse_ad_cues(self, vtt_content: str) -> list[dict]:
        """Parse audio description VTT and extract timing + text."""
        lines = vtt_content.strip().split('\n')
        cues = []
        i = 0
        while i < len(lines):
            line = lines[i].strip()
            if line in ("WEBVTT", "") or line.startswith("NOTE"):
                i += 1
                continue
            if " --> " in line:
                timing_parts = line.split(" --> ")
                start_time = self._parse_timestamp(timing_parts[0].strip())
                end_time = self._parse_timestamp(timing_parts[1].strip())
                i += 1
                text_lines = []
                while i < len(lines) and lines[i].strip():
                    text_lines.append(lines[i].strip())
                    i += 1
                if text_lines:
                    cues.append({"start_time": start_time, "end_time": end_time, "text": " ".join(text_lines)})
            else:
                i += 1
        return cues

    def _parse_timestamp(self, timestamp: str) -> float:
        """Convert VTT timestamp to seconds."""
        parts = timestamp.split(":")
        if len(parts) == 3:
            hours, minutes, seconds = parts
        elif len(parts) == 2:
            hours, minutes, seconds = "0", parts[0], parts[1]
        else:
            raise ValueError(f"Invalid timestamp format: {timestamp}")
        sec_parts = seconds.split(".")
        return (
            int(hours) * 3600
            + int(minutes) * 60
            + int(sec_parts[0])
            + (int(sec_parts[1]) / 1000.0 if len(sec_parts) > 1 else 0)
        )


# Global service instance
gemini_tts_service = GeminiTTSService()
