"""
Google Gemini TTS Adapter

Synthesizes text to speech using Google Cloud Text-to-Speech API
with Gemini models. Returns 16kHz slin16 PCM chunks for direct
injection into DVGateway.

Features:
- Gemini 2.5 Flash/Pro TTS models with natural Korean prosody
- MP3 response → 16kHz PCM conversion via ffmpeg subprocess
- 30 built-in multi-language voices
- Automatic base64 decoding of audioContent response

Model Reference (2026-03):
  gemini-2.5-flash-tts — Fastest, optimized for real-time voice (default)
  gemini-2.5-pro-tts   — Highest quality, richer prosody

Voice options (30 voices):
  Kore (default), Puck, Aoede, Charon, Fenrir, Leda, Orus, Zephyr,
  Achernar, Altair, Autonoe, Callirrhoe, Despina, Erinome, Gacrux,
  Isonoe, Keid, Laomedeia, Lysithea, Maia, Narvi, Orus, Pegasi,
  Pulcherrima, Rasalhague, Sadachbia, Sadaltager, Schedar, Sulafat,
  Umbriel

API Reference: https://cloud.google.com/text-to-speech/docs/reference/rest
"""

from __future__ import annotations

import asyncio
import base64
import struct
from typing import AsyncIterable, AsyncIterator

import aiohttp

from dvgateway.audio.codec import float32_to_slin16
from dvgateway.types import (
    TtsAdapter,
    TtsOptions,
    VoiceInfo,
)

DV_SAMPLE_RATE = 16000
CHUNK_SAMPLES = 320  # 20ms at 16kHz
CHUNK_BYTES = CHUNK_SAMPLES * 2  # 640 bytes per 20ms frame

# ── Built-in Gemini TTS Voices ────────────────────────────────────────────

GEMINI_VOICES: list[VoiceInfo] = [
    VoiceInfo(id="Kore", label="Kore (여성, 기본)"),
    VoiceInfo(id="Puck", label="Puck (남성)"),
    VoiceInfo(id="Aoede", label="Aoede (여성)"),
    VoiceInfo(id="Charon", label="Charon (남성)"),
    VoiceInfo(id="Fenrir", label="Fenrir (남성)"),
    VoiceInfo(id="Leda", label="Leda (여성)"),
    VoiceInfo(id="Orus", label="Orus (남성)"),
    VoiceInfo(id="Zephyr", label="Zephyr (남성)"),
    VoiceInfo(id="Achernar", label="Achernar (여성)"),
    VoiceInfo(id="Altair", label="Altair (남성)"),
    VoiceInfo(id="Autonoe", label="Autonoe (여성)"),
    VoiceInfo(id="Callirrhoe", label="Callirrhoe (여성)"),
    VoiceInfo(id="Despina", label="Despina (여성)"),
    VoiceInfo(id="Erinome", label="Erinome (여성)"),
    VoiceInfo(id="Gacrux", label="Gacrux (남성)"),
    VoiceInfo(id="Isonoe", label="Isonoe (여성)"),
    VoiceInfo(id="Keid", label="Keid (남성)"),
    VoiceInfo(id="Laomedeia", label="Laomedeia (여성)"),
    VoiceInfo(id="Lysithea", label="Lysithea (여성)"),
    VoiceInfo(id="Maia", label="Maia (여성)"),
    VoiceInfo(id="Narvi", label="Narvi (남성)"),
    VoiceInfo(id="Pegasi", label="Pegasi (남성)"),
    VoiceInfo(id="Pulcherrima", label="Pulcherrima (여성)"),
    VoiceInfo(id="Rasalhague", label="Rasalhague (남성)"),
    VoiceInfo(id="Sadachbia", label="Sadachbia (남성)"),
    VoiceInfo(id="Sadaltager", label="Sadaltager (남성)"),
    VoiceInfo(id="Schedar", label="Schedar (남성)"),
    VoiceInfo(id="Sulafat", label="Sulafat (여성)"),
    VoiceInfo(id="Umbriel", label="Umbriel (남성)"),
]


def _pcm_bytes_to_float32(buf: bytes) -> list[float]:
    """Convert little-endian 16-bit PCM bytes to float32 list."""
    num_samples = len(buf) // 2
    samples = struct.unpack(f"<{num_samples}h", buf[: num_samples * 2])
    return [s / 32768.0 for s in samples]


async def _mp3_to_pcm_16k(mp3_data: bytes) -> bytes:
    """Convert MP3 bytes to 16kHz 16-bit mono PCM via ffmpeg subprocess.

    Args:
        mp3_data: Raw MP3 audio bytes.

    Returns:
        Raw 16-bit little-endian PCM bytes at 16kHz mono.

    Raises:
        RuntimeError: If ffmpeg conversion fails.
    """
    proc = await asyncio.create_subprocess_exec(
        "ffmpeg",
        "-i", "pipe:0",
        "-f", "s16le",
        "-acodec", "pcm_s16le",
        "-ar", "16000",
        "-ac", "1",
        "-loglevel", "error",
        "pipe:1",
        stdin=asyncio.subprocess.PIPE,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE,
    )
    stdout, stderr = await proc.communicate(input=mp3_data)

    if proc.returncode != 0:
        err_msg = stderr.decode("utf-8", errors="replace").strip()
        raise RuntimeError(f"ffmpeg MP3→PCM conversion failed: {err_msg}")

    return stdout


class GeminiTtsAdapter(TtsAdapter):
    """Google Gemini TTS adapter implementing the TtsAdapter interface.

    Uses Google Cloud Text-to-Speech API with Gemini models for
    high-quality speech synthesis. Response audio (MP3) is converted
    to 16kHz slin16 PCM via ffmpeg for DVGateway compatibility.

    Args:
        api_key: Google Cloud API key.
        voice: Voice name (default: Kore).
        model: Gemini TTS model ID (default: gemini-2.5-flash-tts).
        language: BCP-47 language code (default: ko-KR).
    """

    def __init__(
        self,
        api_key: str = "",
        voice: str = "Kore",
        model: str = "gemini-2.5-flash-tts",
        language: str = "ko-KR",
    ) -> None:
        self._api_key = api_key
        self._voice = voice
        self._model = model
        self._language = language

    async def _synthesize_impl(self, text: str, options: TtsOptions | None = None) -> AsyncIterator[bytes]:
        """Synthesize text and yield 16kHz slin16 PCM chunks.

        Sends a request to Google Cloud TTS API, receives base64-encoded
        MP3 audio, decodes it, converts to 16kHz PCM via ffmpeg, and
        yields 20ms PCM frames.
        """
        voice = (options.voice_id if options and options.voice_id else None) or self._voice
        language = (options.language if options and options.language else None) or self._language

        url = f"https://texttospeech.googleapis.com/v1/text:synthesize?key={self._api_key}"

        request_body = {
            "input": {"text": text},
            "voice": {
                "languageCode": language,
                "name": voice,
                "modelName": self._model,
            },
            "audioConfig": {
                "audioEncoding": "MP3",
            },
        }

        async with aiohttp.ClientSession() as session:
            async with session.post(
                url,
                headers={"Content-Type": "application/json"},
                json=request_body,
            ) as resp:
                if resp.status != 200:
                    err_body = await resp.text()
                    raise RuntimeError(f"Google Gemini TTS failed (HTTP {resp.status}): {err_body}")

                data = await resp.json()

        audio_content_b64 = data.get("audioContent", "")
        if not audio_content_b64:
            raise RuntimeError("Google Gemini TTS response missing audioContent")

        mp3_data = base64.b64decode(audio_content_b64)

        # Convert MP3 → 16kHz 16-bit PCM via ffmpeg
        pcm_data = await _mp3_to_pcm_16k(mp3_data)

        # Yield 20ms PCM frames (640 bytes = 320 samples at 16kHz)
        offset = 0
        while offset < len(pcm_data):
            end = min(offset + CHUNK_BYTES, len(pcm_data))
            frame = pcm_data[offset:end]
            offset = end

            # Frame is already 16kHz 16-bit PCM — convert to float32 then back
            # to slin16 with clamping for consistency with other adapters
            samples = _pcm_bytes_to_float32(frame)
            yield float32_to_slin16(samples)

    def synthesize(self, text: str, options: TtsOptions | None = None) -> AsyncIterable[bytes]:
        return self._synthesize_impl(text, options)
