"""Jam-Stimme: Horror-Creature-Sample (MP3) → TTS-Fallback."""

from __future__ import annotations

import asyncio
import ctypes
import os
import random
import re
import shutil
import subprocess
import sys
import time
import xml.sax.saxutils
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
SOUNDS_DIR = ROOT / "assets" / "sounds"
VOICE_DIR = ROOT / "assets" / "voice"
LOG_FILE = ROOT / "data" / "jam-voice.log"

CREATURE_VOICE_NAME = "jam_creature.mp3"
CREATURE_VOICE_BUNDLED = VOICE_DIR / CREATURE_VOICE_NAME
CREATURE_VOICE_DOWNLOAD = Path(
    r"c:\Users\david\Downloads\alesiadavina-female-horror-voice-creature-2-attack-vol-001-138132.mp3"
)

# Tiefe Monster-Stimmen (Microsoft Neural)
MONSTER_VOICES = (
    "de-DE-ConradNeural",
    "de-DE-KillianNeural",
)

BABY_VOICE_FILES = [
    "baby_piep.mp3",
    "baby_boop.mp3",
    "baby_mrrp.mp3",
    "baby_blub.mp3",
]
MONSTER_VOICE_FILES = [
    "baby_hallo.mp3",
    "cute_ja.mp3",
    "cute_danke.mp3",
    "cute_hunger.mp3",
]
SFX_FILES = [
    "cute_pop_1.mp3",
    "cute_pop_2.mp3",
    "cute_pop_3.mp3",
    "happy_chime.mp3",
    "little_win.mp3",
]
FART_SFX = "cartoon_fart.mp3"
LAUGH_SFX = "frech_laugh.mp3"

# Lautstärke 0–100 (leise Monster-Geräusche)
VOL_CREATURE = 32
VOL_CREATURE_LOUD = 72
VOL_MUSIC_SING = 96
VOL_SFX = 38
VOL_SFX_LOUD = 52
VOL_FART = 48
VOL_FART_MAX = 58
VOL_LAUGH = 32
VOL_LAUGH_MAX = 42

_pyttsx3_engine = None


def _log(msg: str) -> None:
    try:
        LOG_FILE.parent.mkdir(parents=True, exist_ok=True)
        with LOG_FILE.open("a", encoding="utf-8") as f:
            f.write(f"{time.strftime('%Y-%m-%d %H:%M:%S')} {msg}\n")
    except OSError:
        pass


def _ensure_creature_voice() -> Path | None:
    """Creature-MP3 ins Projekt legen, falls nur in Downloads vorhanden."""
    custom = os.environ.get("JAM_VOICE_MP3", "").strip()
    if custom:
        p = Path(custom)
        if p.is_file():
            return p
    if CREATURE_VOICE_BUNDLED.is_file() and CREATURE_VOICE_BUNDLED.stat().st_size > 1000:
        return CREATURE_VOICE_BUNDLED
    if CREATURE_VOICE_DOWNLOAD.is_file():
        try:
            VOICE_DIR.mkdir(parents=True, exist_ok=True)
            shutil.copy2(CREATURE_VOICE_DOWNLOAD, CREATURE_VOICE_BUNDLED)
            return CREATURE_VOICE_BUNDLED
        except OSError as e:
            _log(f"Copy creature voice failed: {e}")
            return CREATURE_VOICE_DOWNLOAD
    return None


def speak_creature_sync(loud: bool = False) -> bool:
    path = _ensure_creature_voice()
    if not path:
        return False
    vol = VOL_CREATURE_LOUD if loud else VOL_CREATURE
    return _play_mp3_windows(path, vol)


def _pick(folder: Path, names: list[str]) -> Path | None:
    existing = [folder / n for n in names if (folder / n).exists()]
    return random.choice(existing) if existing else None


def _play_mp3_mci(path: Path, volume: int = VOL_SFX) -> bool:
    if not path.exists():
        return False
    try:
        mci = ctypes.windll.winmm.mciSendStringW
        mci("close jamvoice", None, 0, 0)
        p = str(path.resolve())
        vol_mci = int(1000 * max(0, min(100, volume)) / 100)
        for cmd in (
            f'open "{p}" type mpegvideo alias jamvoice',
            f'open "{p}" alias jamvoice',
        ):
            if mci(cmd, None, 0, 0) == 0:
                mci(f"setaudio jamvoice volume to {vol_mci}", None, 0, 0)
                mci("play jamvoice wait", None, 0, 0)
                mci("close jamvoice", None, 0, 0)
                return True
        return False
    except Exception as e:
        _log(f"MCI failed: {e}")
        return False


def _play_mp3_windows(path: Path, volume: int = VOL_SFX) -> bool:
    if not path.exists():
        return False
    from jam import voice_player

    vol_f = max(0.0, min(1.0, volume / 100.0))
    if voice_player.play_mp3(path, volume=vol_f):
        return True
    if _play_mp3_mci(path, volume):
        return True
    p = str(path.resolve()).replace("'", "''")
    vol = max(0, min(100, volume))
    ps = (
        "$p = New-Object -ComObject WMPlayer.OCX; "
        f"$p.settings.volume = {vol}; "
        f"$m = $p.newMedia('{p}'); "
        "$p.controls.currentItem = $m; "
        "$p.controls.play(); "
        "Start-Sleep -Milliseconds 200; "
        "while ($p.playState -eq 3) { Start-Sleep -Milliseconds 60 }"
    )
    try:
        subprocess.run(
            ["powershell", "-NoProfile", "-Sta", "-Command", ps],
            creationflags=subprocess.CREATE_NO_WINDOW,
            timeout=45,
            check=False,
        )
        return True
    except (subprocess.TimeoutExpired, OSError) as e:
        _log(f"WMPlayer failed: {e}")
        return False


def play_sfx(filename: str, volume: int = VOL_SFX) -> bool:
    return _play_mp3_windows(SOUNDS_DIR / filename, volume)


def _fart_beep_fallback() -> None:
    try:
        import winsound

        for freq, dur in ((160, 280), (110, 420), (75, 380)):
            winsound.Beep(freq, dur)
    except (RuntimeError, OSError):
        pass


def _laugh_beep_fallback() -> None:
    try:
        import winsound

        for freq, dur in ((380, 50), (480, 55), (520, 60)):
            winsound.Beep(freq, dur)
    except (RuntimeError, OSError):
        pass


def play_fart_sound(*, loud: bool = False) -> bool:
    vol = VOL_FART_MAX if loud else VOL_FART
    path = SOUNDS_DIR / FART_SFX
    if path.exists() and play_sfx(FART_SFX, vol):
        return True
    _fart_beep_fallback()
    return True


def play_laugh_sound(*, loud: bool = False) -> bool:
    vol = VOL_LAUGH_MAX if loud else VOL_LAUGH
    path = SOUNDS_DIR / LAUGH_SFX
    if path.exists() and play_sfx(LAUGH_SFX, vol):
        return True
    _laugh_beep_fallback()
    return True


def play_fart_and_laugh_sync(*, loud: bool = False, pause_ms: int = 420) -> None:
    """Pupsen, kurze Pause, dann lachen (Hauptthread, standard leise)."""
    play_fart_sound(loud=loud)
    time.sleep(max(0.15, pause_ms / 1000.0))
    play_laugh_sound(loud=loud)
    try:
        from jam.speech import mark_sound_played

        mark_sound_played()
    except ImportError:
        pass


def play_baby_sound() -> bool:
    p = _pick(VOICE_DIR, BABY_VOICE_FILES)
    if p and _play_mp3_windows(p, VOL_SFX):
        return True
    p = _pick(SOUNDS_DIR, SFX_FILES)
    if p:
        return _play_mp3_windows(p, max(20, VOL_SFX - 6))
    return False


def play_monster_line(for_text: str | None = None) -> bool:
    if for_text:
        t = for_text.lower()
        key = None
        if "hunger" in t:
            key = "cute_hunger.mp3"
        elif "danke" in t or "lecker" in t:
            key = "cute_danke.mp3"
        elif "hallo" in t or "jam" in t:
            key = "cute_ja.mp3"
        if key:
            p = VOICE_DIR / key
            if p.exists():
                return _play_mp3_windows(p, VOL_SFX)
    p = _pick(VOICE_DIR, MONSTER_VOICE_FILES + BABY_VOICE_FILES)
    if p:
        return _play_mp3_windows(p, VOL_SFX)
    return play_baby_sound()


def _edge_rate(stage: int, loud: bool) -> str:
    if loud:
        return "+22%"
    if stage <= 0:
        return "+18%"
    if stage <= 2:
        return "+4%"
    return "-6%"


def _edge_pitch(stage: int, loud: bool) -> str:
    """Tiefer = mehr Monster."""
    if loud:
        return "-32Hz"
    if stage <= 0:
        return "-18Hz"
    if stage <= 2:
        return "-26Hz"
    return "-34Hz"


def _pick_monster_voice() -> str:
    return random.choice(MONSTER_VOICES)


def speak_edge_sync(
    text: str,
    stage: int = 1,
    loud: bool = False,
    *,
    fluent: bool = True,
    very_loud: bool = False,
) -> bool:
    from jam.german_tts import speak_edge_fluent, tts_mode

    character = not fluent or tts_mode() == "character"

    def _play(path: Path, vol: int) -> bool:
        if very_loud:
            vol = max(vol, VOL_MUSIC_SING)
        return _play_mp3_windows(path, vol)

    return speak_edge_fluent(
        text,
        stage,
        loud or very_loud,
        character=character,
        very_loud=very_loud,
        play_fn=_play,
    )


def speak_sapi_sync(text: str, stage: int = 1, loud: bool = False) -> bool:
    clean = re.sub(r"['\"\r\n]", " ", text).strip()
    if not clean:
        return False
    safe = xml.sax.saxutils.escape(clean)
    if loud:
        pitch, rate = "-8%", "+8%"
    elif stage <= 0:
        pitch, rate = "+2%", "+10%"
    else:
        pitch, rate = "-4%", "+0%"
    ps = f"""
Add-Type -AssemblyName System.Speech
$s = New-Object System.Speech.Synthesis.SpeechSynthesizer
$s.Volume = 100
$voices = $s.GetInstalledVoices() | ForEach-Object {{ $_.VoiceInfo }}
$pick = $voices | Where-Object {{
    $_.Name -match 'Stefan|Markus|Michael|Hazel|Conrad|Killian|David|Georg'
}} | Select-Object -First 1
if (-not $pick) {{
    $pick = $voices | Where-Object {{ $_.Culture.Name -like 'de*' }} | Select-Object -First 1
}}
if ($pick) {{ $s.SelectVoice($pick.Name) }}
$xml = @"
<speak version='1.0' xmlns='http://www.w3.org/2001/10/synthesis' xml:lang='de-DE'>
<prosody pitch='{pitch}' rate='{rate}'>{safe}</prosody>
</speak>
"@
$s.SpeakSsml($xml)
"""
    try:
        subprocess.run(
            ["powershell", "-NoProfile", "-Sta", "-Command", ps],
            creationflags=subprocess.CREATE_NO_WINDOW,
            timeout=60,
            check=False,
        )
        return True
    except (subprocess.TimeoutExpired, OSError) as e:
        _log(f"SAPI failed: {e}")
        return False


def speak_pyttsx3_sync(text: str, stage: int = 1, loud: bool = False) -> bool:
    global _pyttsx3_engine
    clean = re.sub(r"\*+", "", text).strip()
    if not clean:
        return False
    try:
        import pyttsx3

        if _pyttsx3_engine is None:
            _pyttsx3_engine = pyttsx3.init()
        eng = _pyttsx3_engine
        prefer = ("stefan", "markus", "michael", "david", "hedda", "german", "deutsch")
        for v in eng.getProperty("voices"):
            name = (v.name or "").lower()
            vid = (v.id or "").lower()
            if any(p in name or p in vid for p in prefer[:4]):
                eng.setProperty("voice", v.id)
                break
        else:
            for v in eng.getProperty("voices"):
                name = (v.name or "").lower()
                if "deutsch" in name or "german" in name:
                    eng.setProperty("voice", v.id)
                    break
        if loud:
            eng.setProperty("rate", 195)
        elif stage <= 0:
            eng.setProperty("rate", 200)
        else:
            eng.setProperty("rate", 155)
        eng.setProperty("volume", 1.0)
        eng.say(clean)
        eng.runAndWait()
        return True
    except Exception as e:
        _log(f"pyttsx3 failed: {e}")
        return False


def speak_audible(
    text: str, stage: int = 1, loud: bool = False, *, very_loud: bool = False
) -> bool:
    """
    Standard: fließendes Deutsch (Edge Neural, echter Satz).
    creature-Modus: Horror-MP3. Kurze *piep* → SFX.
    """
    from jam.german_tts import is_creature_only_line, prepare_german_text, tts_mode

    raw = text.strip()
    if not raw:
        return False

    mode = tts_mode()

    effective = loud or very_loud

    if is_creature_only_line(raw):
        if play_baby_sound():
            return True
        return speak_creature_sync(effective)

    if mode == "creature":
        if speak_creature_sync(effective):
            return True

    clean = prepare_german_text(raw)

    if speak_edge_sync(
        clean, stage, effective, fluent=(mode == "fluent"), very_loud=very_loud
    ):
        return True
    if speak_sapi_sync(clean, stage, effective):
        return True
    if speak_pyttsx3_sync(clean, stage, effective):
        return True
    if mode != "fluent" and speak_creature_sync(effective):
        return True
    if play_monster_line(clean):
        return True
    ok = play_baby_sound()
    if not ok:
        _log(f"All voice backends failed for: {clean[:80]}")
    return ok


def has_voice_pack() -> bool:
    return _ensure_creature_voice() is not None or any(
        (VOICE_DIR / n).exists() for n in BABY_VOICE_FILES
    )


def speak_online_thread(text: str, stage: int, loud: bool) -> None:
    speak_audible(text, stage, loud)


# Abwärtskompatibel
play_cute_line = play_monster_line
