"""Faseeh Arabic text-to-speech service implementation for Pipecat.

This module provides TTS functionality using Faseeh's API, specializing in
high-quality Arabic speech synthesis with streaming support.

Features:
    - HTTP streaming of PCM16 audio at 24kHz mono
    - Configurable voice, model, stability, and speed parameters
    - Runtime settings updates (voice, model, speed, stability)
    - OpenTelemetry tracing via @traced_tts
"""

import os
import time
from typing import Any, AsyncGenerator, Mapping, Optional

import aiohttp
from loguru import logger
from pydantic import BaseModel, Field

from pipecat.frames.frames import (
    ErrorFrame,
    Frame,
    TTSAudioRawFrame,
    TTSStartedFrame,
    TTSStoppedFrame,
)
from pipecat.services.tts_service import TTSService
from pipecat.utils.tracing.service_decorators import traced_tts

FASEEH_API_BASE_URL = "https://api.faseeh.ai/api/v1"


class FaseehTTSService(TTSService):
    """Faseeh Arabic text-to-speech service.

    Converts text to speech using Faseeh's TTS API with support for multiple
    Arabic voices and models. Uses HTTP streaming to deliver low-latency
    PCM16 audio at 24kHz mono.

    Example::

        tts = FaseehTTSService(
            api_key="your-api-key",
            aiohttp_session=session,
            voice_id="ar-hijazi-female-2",
            model="faseeh-v1-preview",
        )

        # With custom parameters
        tts = FaseehTTSService(
            api_key="your-api-key",
            aiohttp_session=session,
            params=FaseehTTSService.InputParams(stability=0.7, speed=1.1),
        )
    """

    class InputParams(BaseModel):
        """Voice configuration parameters.

        Parameters:
            stability: Voice consistency (0.0 = more variable, 1.0 = more consistent).
            speed: Speech rate multiplier (0.7 = slower, 1.2 = faster).
        """

        stability: float = Field(default=0.5, ge=0.0, le=1.0)
        speed: float = Field(default=1.0, ge=0.7, le=1.2)

    def __init__(
        self,
        *,
        api_key: Optional[str] = None,
        aiohttp_session: aiohttp.ClientSession,
        voice_id: str = "ar-hijazi-female-2",
        model: str = "faseeh-v1-preview",
        base_url: str = FASEEH_API_BASE_URL,
        sample_rate: Optional[int] = None,
        params: Optional[InputParams] = None,
        **kwargs,
    ):
        """Initialize the Faseeh TTS service.

        Args:
            api_key: Faseeh API key. Falls back to FASEEH_API_KEY env var.
            aiohttp_session: Shared aiohttp session for making requests.
            voice_id: Voice identifier. Defaults to "ar-hijazi-female-2".
            model: TTS model name. Defaults to "faseeh-v1-preview".
            base_url: Faseeh API base URL.
            sample_rate: Audio sample rate in Hz. Defaults to 24000.
            params: Voice configuration parameters.
            **kwargs: Additional arguments passed to parent TTSService.

        Raises:
            ValueError: If no API key is provided or found in environment.
        """
        super().__init__(sample_rate=sample_rate or 24000, **kwargs)

        self._api_key = api_key or os.environ.get("FASEEH_API_KEY", "")
        if not self._api_key:
            raise ValueError(
                "Faseeh API key is required. Pass api_key or set FASEEH_API_KEY env var."
            )

        self._session = aiohttp_session
        self._base_url = base_url

        params = params or FaseehTTSService.InputParams()
        self._settings = {
            "stability": params.stability,
            "speed": params.speed,
        }

        self.set_model_name(model)
        self.set_voice(voice_id)

    def can_generate_metrics(self) -> bool:
        """Check if this service can generate processing metrics.

        Returns:
            True, as Faseeh service supports metrics generation.
        """
        return True

    async def _update_settings(self, settings: Mapping[str, Any]):
        """Update service settings at runtime.

        Called when a TTSUpdateSettingsFrame arrives in the pipeline.

        Args:
            settings: Mapping of setting names to new values.
        """
        await super()._update_settings(settings)
        for key, value in settings.items():
            if key == "stability":
                self._settings["stability"] = value
            elif key == "speed":
                self._settings["speed"] = value

    @traced_tts
    async def run_tts(self, text: str, context_id: str) -> AsyncGenerator[Frame, None]:
        """Generate speech from text using Faseeh's TTS API.

        Streams PCM16 audio chunks from Faseeh's HTTP endpoint, yielding
        TTSAudioRawFrame objects as data arrives.

        Args:
            text: The text to synthesize into speech.
            context_id: Unique identifier for this TTS context.

        Yields:
            Frame: TTSStartedFrame, TTSAudioRawFrame(s), TTSStoppedFrame, or ErrorFrame.
        """
        logger.debug(f"{self}: Generating TTS [{text}]")

        url = f"{self._base_url}/text-to-speech/{self.model_name}"
        headers = {
            "x-api-key": self._api_key,
            "Content-Type": "application/json",
        }
        payload = {
            "voice_id": self._voice_id,
            "text": text,
            "model": self.model_name,
            "stability": self._settings["stability"],
            "speed": self._settings["speed"],
            "streaming": True,
        }

        try:
            await self.start_ttfb_metrics()
            request_start = time.perf_counter()
            first_chunk_received = False

            async with self._session.post(url, headers=headers, json=payload) as response:
                if response.status != 200:
                    error_text = await response.text()
                    yield ErrorFrame(error=self._parse_error(response.status, error_text))
                    return

                await self.start_tts_usage_metrics(text)
                yield TTSStartedFrame(context_id=context_id)

                audio_buffer = bytearray()
                async for chunk, _ in response.content.iter_chunks():
                    if chunk:
                        if not first_chunk_received:
                            first_chunk_received = True
                            ttfb = time.perf_counter() - request_start
                            logger.debug(f"{self} TTFB: {ttfb:.3f}s")
                        await self.stop_ttfb_metrics()
                        audio_buffer.extend(chunk)

                        # PCM16 = 2 bytes per sample. Yield only aligned data.
                        aligned_length = len(audio_buffer) & ~1
                        if aligned_length > 0:
                            yield TTSAudioRawFrame(
                                audio=bytes(audio_buffer[:aligned_length]),
                                sample_rate=self.sample_rate,
                                num_channels=1,
                                context_id=context_id,
                            )
                            audio_buffer = audio_buffer[aligned_length:]

                # Flush any remaining complete samples
                if len(audio_buffer) >= 2:
                    aligned_length = len(audio_buffer) & ~1
                    yield TTSAudioRawFrame(
                        audio=bytes(audio_buffer[:aligned_length]),
                        sample_rate=self.sample_rate,
                        num_channels=1,
                        context_id=context_id,
                    )

        except aiohttp.ClientError as e:
            yield ErrorFrame(error=f"Faseeh connection error: {e}")
        except Exception as e:
            yield ErrorFrame(error=f"Faseeh TTS error: {e}")
        finally:
            yield TTSStoppedFrame(context_id=context_id)

    def _parse_error(self, status: int, body: str) -> str:
        """Map Faseeh HTTP errors to descriptive messages.

        Args:
            status: HTTP status code.
            body: Response body text.

        Returns:
            A human-readable error message.
        """
        error_messages = {
            400: f"Bad request: {body}",
            401: "Unauthorized - check your FASEEH_API_KEY",
            402: f"Insufficient balance - add credits at app.faseeh.ai: {body}",
            403: f"Access denied - model/feature not enabled: {body}",
            404: "Voice or model not found - check voice_id and model parameters",
            429: "Rate limit exceeded - reduce request frequency",
        }
        return error_messages.get(status, f"Faseeh API error (status {status}): {body}")
