"""
ElevenLabs TTS Adapter

Synthesizes text to speech using ElevenLabs' streaming API.
Returns 16kHz slin16 PCM chunks for direct injection into DVGateway.

Features:
- Streaming output (first audio chunk ≤75ms latency with Flash models)
- Automatic 24kHz → 16kHz resampling
- Human-like voice optimization (Korean/English presets)
- Korean native voice support (9 built-in voices)
- Voice fetching (list all available voices including cloned)
- Voice cloning (create custom voices from audio)

ElevenLabs Model Reference (2026-03):
  eleven_flash_v2_5      — Fastest (~75ms TTFA), best for real-time voice (default)
  eleven_turbo_v2_5      — Balanced quality/speed (~200ms TTFA)
  eleven_multilingual_v2 — Highest multilingual quality (higher latency)
  eleven_multilingual_v3 — Next-gen multilingual, improved prosody (2026)

Korean Voice IDs (built-in):
  pjJMvFj0JGWi3mogOkHH  — Hyun Bin (남성)
  t0jbNlBVZ17f02VDIeMI  — 지영 / JiYoung (여성)
  zrHiDhphv9ZnVXBqCLjz  — Jennie (여성)
  ZJCNdOEhQGMOIbMuhBME  — Han Aim (남성)
  ova4yY2jqnnUdGOmTGbx  — KKC HQ (남성)
  Xb7hH8MSUJpSbSDYk0k2  — Anna Kim (여성)
  XrExE9yKIg1WjnnlVkGX  — Yuna (여성)
  ThT5KcBeYPX3keUQqHPh  — Jina (여성)
  Sita5M0jWFxPiECPABjR  — jjeong (여성)

API Endpoint: POST https://api.elevenlabs.io/v1/text-to-speech/{voiceId}/stream
"""

from __future__ import annotations

import struct
from typing import AsyncIterable, AsyncIterator

import aiohttp

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

ELEVENLABS_SAMPLE_RATE = 24000
DV_SAMPLE_RATE = 16000

# ── Built-in Korean voice IDs ──────────────────────────────────────────────
KOREAN_VOICES: list[VoiceInfo] = [
    VoiceInfo(id="pjJMvFj0JGWi3mogOkHH", label="Hyun Bin (남성, 한국어)"),
    VoiceInfo(id="t0jbNlBVZ17f02VDIeMI", label="지영 / JiYoung (여성, 한국어)"),
    VoiceInfo(id="zrHiDhphv9ZnVXBqCLjz", label="Jennie (여성, 한국어)"),
    VoiceInfo(id="ZJCNdOEhQGMOIbMuhBME", label="Han Aim (남성, 한국어)"),
    VoiceInfo(id="ova4yY2jqnnUdGOmTGbx", label="KKC HQ (남성, 한국어)"),
    VoiceInfo(id="Xb7hH8MSUJpSbSDYk0k2", label="Anna Kim (여성, 한국어)"),
    VoiceInfo(id="XrExE9yKIg1WjnnlVkGX", label="Yuna (여성, 한국어)"),
    VoiceInfo(id="ThT5KcBeYPX3keUQqHPh", label="Jina (여성, 한국어)"),
    VoiceInfo(id="Sita5M0jWFxPiECPABjR", label="jjeong (여성, 한국어)"),
]


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]


class ElevenLabsAdapter(TtsAdapter):
    """ElevenLabs TTS adapter implementing the TtsAdapter interface.

    Supports human-like voice optimization via ``human_voice`` parameter.
    When enabled (default), adjusts stability, style, and model selection
    for natural-sounding Korean speech.

    Args:
        api_key: ElevenLabs API key.
        voice_id: Voice ID (default: Rachel).
        model: Model ID. When human_voice is enabled, defaults to
            ``eleven_multilingual_v2`` for best Korean prosody.
        stability: Voice stability (0.0–1.0). Lower = more natural variation.
        similarity_boost: Similarity boost (0.0–1.0).
        style: Style exaggeration (0.0–1.0). Higher = more expressive.
        use_speaker_boost: Boost speaker clarity.
        output_format: Output format (default: pcm_24000).
        optimize_streaming_latency: Latency optimization level (0–4).
        human_voice: Human-like voice options. Default: Korean preset.
            Set to ``None`` with explicit parameters to disable.
    """

    def __init__(
        self,
        api_key: str = "",
        voice_id: str = "21m00Tcm4TlvDq8ikWAM",
        model: str | None = None,
        stability: float | None = None,
        similarity_boost: float = 0.75,
        style: float | None = None,
        use_speaker_boost: bool = True,
        output_format: str = "pcm_24000",
        optimize_streaming_latency: int | None = None,
        human_voice: HumanVoiceOptions | None | bool = True,
    ) -> None:
        self._api_key = api_key
        self._voice_id = voice_id
        self._similarity_boost = similarity_boost
        self._use_speaker_boost = use_speaker_boost
        self._output_format = output_format

        # Resolve human voice: True → Korean defaults, False/None → disabled
        if human_voice is True:
            hv = HUMAN_VOICE_DEFAULTS_KO
        elif isinstance(human_voice, HumanVoiceOptions):
            hv = human_voice
        else:
            hv = None

        hv_enabled = hv is not None

        self._model = model or ("eleven_multilingual_v2" if hv_enabled else "eleven_flash_v2_5")
        self._stability = stability if stability is not None else (1.0 - hv.speech_variation if hv_enabled else 0.5)
        self._style = style if style is not None else (hv.emotional_range if hv_enabled else 0.0)
        self._optimize_streaming_latency = (
            optimize_streaming_latency if optimize_streaming_latency is not None else (3 if hv_enabled else 4)
        )
        self._human_voice = hv

    @staticmethod
    def _is_v3_model(model: str) -> bool:
        """Check if model is v3+ which doesn't support optimize_streaming_latency."""
        return "_v3" in model

    async def _synthesize_impl(self, text: str, options: TtsOptions | None = None) -> AsyncIterator[bytes]:
        voice_id = (options.voice_id if options and options.voice_id else None) or self._voice_id
        url = f"https://api.elevenlabs.io/v1/text-to-speech/{voice_id}/stream?output_format={self._output_format}"
        if not self._is_v3_model(self._model):
            url += f"&optimize_streaming_latency={self._optimize_streaming_latency}"

        speed = options.speed if options and options.speed else 1.0

        async with aiohttp.ClientSession() as session:
            async with session.post(
                url,
                headers={
                    "xi-api-key": self._api_key,
                    "Content-Type": "application/json",
                    "Accept": "audio/pcm",
                },
                json={
                    "text": text,
                    "model_id": self._model,
                    "voice_settings": {
                        "stability": self._stability,
                        "similarity_boost": self._similarity_boost,
                        "style": self._style,
                        "use_speaker_boost": self._use_speaker_boost,
                        "speed": speed,
                    },
                },
            ) as resp:
                if resp.status != 200:
                    err_body = await resp.text()
                    raise RuntimeError(f"ElevenLabs TTS failed (HTTP {resp.status}): {err_body}")

                # PCM 24kHz frames: 960 bytes = 480 samples × 2 bytes = 20ms
                CHUNK_BYTES = 960
                remainder = b""

                async for data in resp.content.iter_any():
                    remainder += data

                    while len(remainder) >= CHUNK_BYTES:
                        frame = remainder[:CHUNK_BYTES]
                        remainder = remainder[CHUNK_BYTES:]

                        samples_24k = _pcm_bytes_to_float32(frame)
                        samples_16k = resample(samples_24k, ELEVENLABS_SAMPLE_RATE, DV_SAMPLE_RATE)
                        yield float32_to_slin16(samples_16k)

                # Flush remaining bytes
                if remainder:
                    samples_24k = _pcm_bytes_to_float32(remainder)
                    samples_16k = resample(samples_24k, ELEVENLABS_SAMPLE_RATE, DV_SAMPLE_RATE)
                    yield float32_to_slin16(samples_16k)

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

    # ── Voice Management APIs ──────────────────────────────────────────────

    @staticmethod
    async def fetch_voices(api_key: str) -> list[VoiceInfo]:
        """Fetch all voices available to the user from ElevenLabs API.

        Returns default voices, cloned voices, and shared library voices.
        Cloned voices are labeled with ``(클론)``, generated with ``(생성됨)``.

        Args:
            api_key: ElevenLabs API key.

        Returns:
            List of VoiceInfo with id and label.
        """
        async with aiohttp.ClientSession() as session:
            async with session.get(
                "https://api.elevenlabs.io/v1/voices",
                headers={"xi-api-key": api_key},
            ) as resp:
                if resp.status != 200:
                    err_body = await resp.text()
                    raise RuntimeError(f"ElevenLabs fetch voices failed (HTTP {resp.status}): {err_body}")

                data = await resp.json()
                voices: list[VoiceInfo] = []
                for v in data.get("voices", []):
                    name = v.get("name", "Unknown")
                    category = v.get("category", "")
                    labels = v.get("labels", {})

                    # Build label with category tags
                    tag = ""
                    if category == "cloned":
                        tag = " (클론)"
                    elif category == "generated":
                        tag = " (생성됨)"
                    elif category == "professional":
                        tag = " (프로)"

                    lang = ""
                    if labels.get("language"):
                        lang = f" [{labels['language']}]"

                    voices.append(VoiceInfo(
                        id=v.get("voice_id", ""),
                        label=f"{name}{tag}{lang}",
                    ))

                return voices

    @staticmethod
    async def clone_voice(
        api_key: str,
        name: str,
        audio_data: bytes,
        file_name: str = "voice.wav",
        description: str = "",
    ) -> VoiceInfo:
        """Clone a voice from an audio file using ElevenLabs API.

        Args:
            api_key: ElevenLabs API key.
            name: Name for the cloned voice.
            audio_data: Raw audio file bytes (WAV, MP3, OGG, etc.).
            file_name: Original file name (for MIME type detection).
            description: Optional voice description.

        Returns:
            VoiceInfo with the new voice's id and name.
        """
        form = aiohttp.FormData()
        form.add_field("name", name)
        if description:
            form.add_field("description", description)
        form.add_field("files", audio_data, filename=file_name, content_type="audio/mpeg")

        async with aiohttp.ClientSession() as session:
            async with session.post(
                "https://api.elevenlabs.io/v1/voices/add",
                headers={"xi-api-key": api_key},
                data=form,
            ) as resp:
                if resp.status != 200:
                    err_body = await resp.text()
                    raise RuntimeError(f"ElevenLabs voice clone failed (HTTP {resp.status}): {err_body}")

                data = await resp.json()
                return VoiceInfo(
                    id=data.get("voice_id", ""),
                    label=data.get("name", name),
                )
