"""Tests fuer den Cartesia-TTS-Provider: Voice-Map, Geschlechts-Aufloesung,
nicht unterstuetzte Sprachen und der Cartesia->Piper-Fallback (HTTP gemockt)."""

import asyncio

import httpx
import pytest

from app.providers.tts.cartesia import CartesiaTTSProvider, _parse_voice_map
from app.providers.fallback import FallbackTTSProvider

# Eine Zeile pro 'lang:gender:uuid:name'. Kommentare und ungueltige Zeilen testen wir mit.
VOICES = """
de:m:uuid-de-m:Sebastian
de:f:uuid-de-f:Alina
en:f:uuid-en-f:Gemma
# diese Zeile ist ein Kommentar und wird ignoriert
zh:m:uuid-zh-m:Kai
"""

REQ = httpx.Request("POST", "https://api.cartesia.ai/tts/bytes")


def _run(coro):
    return asyncio.run(coro)


def _provider():
    return CartesiaTTSProvider("key", "DEFAULT-UUID", "sonic-turbo", 24000, voices_str=VOICES)


# --- Voice-Map-Parsing -----------------------------------------------------

def test_parse_voice_map_valid_and_comments():
    m = _parse_voice_map(VOICES)
    assert m[("de", "m")]["uuid"] == "uuid-de-m"
    assert m[("de", "f")]["name"] == "Alina"
    assert ("en", "f") in m
    assert ("zh", "m") in m


def test_parse_voice_map_ignores_invalid_gender():
    assert _parse_voice_map("de:x:uuid:Name") == {}


def test_parse_voice_map_ignores_short_lines():
    assert _parse_voice_map("de:m:nur-drei-teile") == {}


# --- _resolve_voice_id: Praezedenz -----------------------------------------

def test_resolve_exact_gender():
    p = _provider()
    assert p._resolve_voice_id("de", "m") == "uuid-de-m"
    assert p._resolve_voice_id("de", "f") == "uuid-de-f"


def test_resolve_any_prefers_female():
    assert _provider()._resolve_voice_id("de", "any") == "uuid-de-f"


def test_resolve_falls_back_to_other_gender():
    # en hat nur eine weibliche Stimme -> Anfrage 'm' faellt auf 'f' zurueck.
    assert _provider()._resolve_voice_id("en", "m") == "uuid-en-f"


def test_resolve_unknown_language_uses_default():
    assert _provider()._resolve_voice_id("xx", "f") == "DEFAULT-UUID"


# --- synthesize (HTTP gemockt) ---------------------------------------------

def _patch_post(monkeypatch, response):
    captured = {}

    async def fake_post(self, url, **kw):
        captured["json"] = kw.get("json")
        return response

    monkeypatch.setattr(httpx.AsyncClient, "post", fake_post)
    return captured


def test_synthesize_returns_audio_and_resolves_gender(monkeypatch):
    cap = _patch_post(monkeypatch, httpx.Response(200, content=b"PCM", request=REQ))
    out = _run(_provider().synthesize("Hallo", voice="f", language="de"))
    assert out == b"PCM"
    assert cap["json"]["voice"]["id"] == "uuid-de-f"


def test_synthesize_direct_uuid_passthrough(monkeypatch):
    cap = _patch_post(monkeypatch, httpx.Response(200, content=b"X", request=REQ))
    _run(_provider().synthesize("Hi", voice="custom-uuid-123", language="de"))
    assert cap["json"]["voice"]["id"] == "custom-uuid-123"


@pytest.mark.parametrize("lang", ["ar", "it", "ru", "pl"])
def test_synthesize_unsupported_language_raises(monkeypatch, lang):
    _patch_post(monkeypatch, httpx.Response(200, content=b"X", request=REQ))
    with pytest.raises(RuntimeError, match="unterst"):
        _run(_provider().synthesize("x", voice="f", language=lang))


def test_synthesize_empty_key_raises():
    p = CartesiaTTSProvider("", "DEF", "sonic-turbo", 24000, voices_str=VOICES)
    with pytest.raises(ValueError, match="API_KEY"):
        _run(p.synthesize("x", voice="f", language="de"))


def test_synthesize_empty_text_raises():
    with pytest.raises(ValueError, match="empty"):
        _run(_provider().synthesize("   ", voice="f", language="de"))


# --- Cartesia -> Piper Fallback fuer nicht unterstuetzte Sprachen ----------

def test_cartesia_to_piper_fallback_for_unsupported_language():
    """Cartesia kann IT nicht -> FallbackTTSProvider reicht den Gender-Code an Piper durch."""
    cartesia = _provider()  # wirft RuntimeError fuer it/ar/ru/pl (vor jedem HTTP-Call)

    class PiperStub:
        async def synthesize(self, text, voice=None, audio_format="pcm", language=None):
            return b"PIPER:" + (voice or "").encode()

    chain = FallbackTTSProvider("tts", [("cartesia", cartesia), ("piper", PiperStub())])
    out = _run(chain.synthesize("Ciao", voice="f", language="it"))
    assert out == b"PIPER:f"  # Gender-Code unveraendert an Piper weitergereicht
