"""Musik erkennen, YouTube-Lieder wählen, Tanz-Modus."""

from __future__ import annotations

import random
import re
import time
import webbrowser
from dataclasses import dataclass
from urllib.parse import quote_plus

from jam.brain import JamBrain, JamState
from jam.owner import owner_name

MUSIC_HINTS = re.compile(
    r"(youtube|youtu\.be|music\.youtube|spotify|soundcloud|tidal|deezer|"
    r"amazon music|vlc|media player|groove|winamp|foobar|apple music|"
    r"bandcamp|mixcloud|musik|music|audio|wmplayer|wiedergabe|playing)",
    re.I,
)

# Jam wählt aus diesen Vorschlägen (Suche auf YouTube)
SONG_PICKS: tuple[dict[str, str], ...] = (
    {"id": "lofi", "title": "Lofi Chill", "query": "lofi hip hop chill study beats"},
    {"id": "happy", "title": "Happy Vibes", "query": "happy music no copyright short"},
    {"id": "gaming", "title": "Gaming Musik", "query": "gaming music no copyright"},
    {"id": "cute", "title": "Cute Song", "query": "cute fun music animation"},
    {"id": "deutsch", "title": "Deutsch Pop", "query": "deutsche musik hits"},
    {"id": "night", "title": "Night Drive", "query": "synthwave night drive music"},
    {"id": "dance", "title": "Dance Party", "query": "dance party music mix"},
    {"id": "minecraft", "title": "Minecraft Vibes", "query": "minecraft music calm"},
)

CATEGORY_ALIASES: dict[str, str] = {
    "lofi": "lofi",
    "chill": "lofi",
    "studieren": "lofi",
    "study": "lofi",
    "happy": "happy",
    "fröhlich": "happy",
    "froehlich": "happy",
    "lustig": "happy",
    "gaming": "gaming",
    "zock": "gaming",
    "game": "gaming",
    "cute": "cute",
    "süß": "cute",
    "suss": "cute",
    "deutsch": "deutsch",
    "german": "deutsch",
    "night": "night",
    "synth": "night",
    "dance": "dance",
    "tanz": "dance",
    "tanzen": "dance",
    "minecraft": "minecraft",
}

NEXT_WORDS = (
    "nächstes",
    "naechstes",
    "anderes",
    "neues",
    "weiteres",
    "nächste",
    "naechste",
    "random",
    "zufällig",
    "zufaellig",
    "wechsel",
)

PICK_WORDS = (
    "wähle",
    "waehle",
    "such",
    "suche",
    "spiel",
    "play",
    "hör",
    "hoer",
    "höre",
    "hoere",
    "mach",
    "zeig",
    "starte",
    "start",
    "öffne",
    "oeffne",
)

SONG_WORDS = ("lied", "song", "track", "video", "musik", "music")

MUSIC_LOVE_LINES = [
    "Boah, Musik! DAS ist toll!",
    "Ich liebe Musik! Ich tanz gleich!",
    "Ohhh ja, das klingt super!",
    "Musik = Jam-Glück!",
    "Darf ich mitwippen? Ich wippe schon!",
    "YouTube + Musik = beste Erfindung!",
]

DANCE_LINES = [
    "Tanzmodus an!",
    "Wheee! Ich tanz!",
    "Links rechts hüpf hüpf!",
    "Musik macht mich wild!",
    "Ich bin ein Tanz-Monster!",
]

# Jam singt laut mit, wenn Musik läuft
SING_LINES = (
    "La la la! {owner}, Musik!",
    "Tra la la – ich sing mit!",
    "Ohhh ja! La la la la!",
    "Jam singt laut: la la la!",
    "Mitmusik! La la la la la!",
    "Boah, {owner}! La la la – so schön!",
    "Ich sing mit! Tra la la tra la!",
    "Musik an – Jam singt: la la la!",
    "Wheee! La la la la la la!",
    "Sing mit mir: la la la!",
)

MUSIC_REACTION_GAP_SEC = 18.0

YOUTUBE_LINES = [
    "Ich mach YouTube auf – Musik time!",
    "YouTube öffnen… ich hör zu!",
    "Gleich kommt Musik aus dem Internet!",
]

PICK_LINES = [
    "{owner}, ich wähl: {title}!",
    "Okay {owner}, auf YouTube: {title}!",
    "Lied-Auswahl: {title} – viel Spaß!",
    "Jam wählt {title} für dich, {owner}!",
]


@dataclass
class YouTubeRequest:
    kind: str  # search | next | catalog
    query: str = ""
    pick_id: str = ""
    title: str = ""


def is_music_playing(title: str) -> bool:
    if not title or len(title) < 3:
        return False
    if "jam" in title.lower() and "python" in title.lower():
        return False
    return bool(MUSIC_HINTS.search(title))


def youtube_search_url(query: str) -> str:
    q = query.strip()
    if not q:
        return "https://music.youtube.com"
    if q.startswith("http://") or q.startswith("https://"):
        return q
    return f"https://www.youtube.com/results?search_query={quote_plus(q)}"


def _pick_by_id(pick_id: str) -> dict[str, str] | None:
    for p in SONG_PICKS:
        if p["id"] == pick_id:
            return p
    return None


def _catalog_from_heard(heard: str) -> dict[str, str] | None:
    h = heard.lower()
    for alias, pick_id in CATEGORY_ALIASES.items():
        if re.search(rf"\b{re.escape(alias)}\b", h):
            return _pick_by_id(pick_id)
    return None


def _strip_noise(text: str) -> str:
    t = text.lower().replace("jam", " ")
    t = re.sub(
        r"\b(youtube|youtu\.be|music\.youtube|bitte|mal|jetzt|auf|das|den|die|ein|eine|mir|dir)\b",
        " ",
        t,
        flags=re.I,
    )
    for w in PICK_WORDS + SONG_WORDS + NEXT_WORDS:
        t = re.sub(rf"\b{re.escape(w)}\b", " ", t, flags=re.I)
    return " ".join(t.split()).strip()


def parse_youtube_request(heard: str) -> YouTubeRequest | None:
    """Sprache → Lied wählen, z. B. „Jam wähle Lied Lofi“ oder „Jam spiel Believer“."""
    h = heard.lower().replace("jam", " ").strip()
    if not h:
        return None

    has_yt = "youtube" in h or "youtu" in h
    has_song = any(w in h for w in SONG_WORDS) or has_yt
    has_pick = any(w in h for w in PICK_WORDS)
    has_next = any(w in h for w in NEXT_WORDS)
    has_musik = "musik" in h or "music" in h

    if has_next and (has_song or has_yt or has_musik):
        return YouTubeRequest(kind="next")

    cat = _catalog_from_heard(h)
    if cat and (has_pick or has_song or has_yt or has_musik):
        return YouTubeRequest(
            kind="catalog", pick_id=cat["id"], title=cat["title"], query=cat["query"]
        )

    # „spiel believer“, „wähle despacito“, „youtube lofi“
    query = ""
    patterns = (
        r"(?:spiel|play|wähle|waehle|such|suche|hör|hoer|höre|hoere|mach|zeig|starte|start|öffne|oeffne)\s+(?:lied|song|musik|music|track|video)?\s*(.+)",
        r"(?:lied|song|musik|music)\s+(.+)",
        r"youtube\s+(.+)",
    )
    for pat in patterns:
        m = re.search(pat, h, re.I)
        if m:
            query = _strip_noise(m.group(1))
            if query:
                break

    if not query:
        query = _strip_noise(h)

    if query and len(query) >= 2:
        return YouTubeRequest(kind="search", query=query, title=query)

    if has_yt or has_musik or (has_song and has_pick):
        return YouTubeRequest(kind="next")

    return None


def can_open_youtube(state: JamState, *, user_pick: bool = False) -> tuple[bool, str]:
    cooldown = 25 if user_pick else 900
    if time.time() - state.last_youtube_open < cooldown:
        return False, "YouTube hab ich gerade erst aufgemacht – kurz warten!"
    return True, ""


def _advance_pick_index(state: JamState) -> dict[str, str]:
    idx = getattr(state, "youtube_pick_index", 0) % len(SONG_PICKS)
    pick = SONG_PICKS[idx]
    state.youtube_pick_index = (idx + 1) % len(SONG_PICKS)
    return pick


def _open_url(brain: JamBrain, url: str, label: str, *, user_pick: bool) -> str:
    ok, msg = can_open_youtube(brain.state, user_pick=user_pick)
    if not ok:
        return msg
    try:
        webbrowser.open(url, new=2)
        brain.state.last_youtube_open = time.time()
        brain.state.youtube_opens += 1
        brain.state.music_love = min(1.0, brain.state.music_love + 0.1)
        brain.state.happiness = min(1.0, brain.state.happiness + 0.2)
        brain.state.add_xp(10)
        brain.state.learn_word("musik")
        brain.state.last_youtube_query = label[:120]
        brain.state.remember(f"YouTube Lied: {label}")
        brain.state.last_action = "youtube_song"
        brain.save()
        owner = owner_name(brain.state)
        if user_pick and label:
            return random.choice(PICK_LINES).format(owner=owner, title=label)
        return random.choice(YOUTUBE_LINES)
    except OSError as e:
        return f"YouTube klappt nicht: {e}"


def open_youtube_request(brain: JamBrain, req: YouTubeRequest) -> str:
    """Lied nach Sprachbefehl öffnen."""
    if req.kind == "catalog" and req.query:
        url = youtube_search_url(req.query)
        return _open_url(brain, url, req.title or req.query, user_pick=True)

    if req.kind == "search" and req.query:
        url = youtube_search_url(req.query)
        return _open_url(brain, url, req.query, user_pick=True)

    if req.kind == "next":
        pick = _advance_pick_index(brain.state)
        url = youtube_search_url(pick["query"])
        return _open_url(brain, url, pick["title"], user_pick=True)

    return open_youtube_music(brain, user_pick=False)


def open_youtube_music(brain: JamBrain, *, query: str | None = None, user_pick: bool = False) -> str:
    """YouTube öffnen – mit optionaler Suche oder Zufalls-/Katalog-Auswahl."""
    if query and query.strip():
        url = youtube_search_url(query.strip())
        return _open_url(brain, url, query.strip(), user_pick=True)

    if user_pick:
        pick = _advance_pick_index(brain.state)
        url = youtube_search_url(pick["query"])
        return _open_url(brain, url, pick["title"], user_pick=True)

    ok, msg = can_open_youtube(brain.state, user_pick=False)
    if not ok:
        return msg
    pick = random.choice(SONG_PICKS)
    url = youtube_search_url(pick["query"])
    return _open_url(brain, url, pick["title"], user_pick=False)


def handle_music_speech(brain: JamBrain, heard: str) -> str | None:
    """
    Musik-Befehle aus Sprache.
    Returns Antworttext oder None wenn kein Musik-Befehl.
    """
    heard_l = heard.lower()
    req = parse_youtube_request(heard)
    if req:
        return open_youtube_request(brain, req)

    if any(x in heard_l for x in ("musik", "music", "tanz", "tanzen", "youtube")):
        if "youtube" in heard_l or any(w in heard_l for w in PICK_WORDS):
            if any(w in heard_l for w in SONG_WORDS) or any(w in heard_l for w in PICK_WORDS):
                return open_youtube_music(brain, user_pick=True)
            return open_youtube_music(brain, user_pick=False)
        return "__dance__"

    if heard_l.strip() in ("youtube", "youtube öffnen", "youtube oeffnen"):
        return open_youtube_music(brain, user_pick=True)

    return None


def pick_sing_line(brain: JamBrain, title: str = "") -> str:
    """Lauter Sing-Satz (mit Namen)."""
    owner = owner_name(brain.state)
    line = random.choice(SING_LINES).format(owner=owner)
    if title and len(title) > 4:
        short = title[:40].strip()
        if short and random.random() < 0.35:
            line = f"La la la! {short}! Tra la la!"
    return line


def should_sing_while_playing(state: JamState) -> bool:
    """Während Musik läuft: ab und zu nochmal laut mitsingen."""
    from jam.speech import MUSIC_SING_GAP_SEC

    return time.time() - getattr(state, "last_music_sing_at", 0.0) >= MUSIC_SING_GAP_SEC


def on_music_detected(brain: JamBrain, title: str) -> str | None:
    """Reaktion wenn Musik erkannt wird – lauter Sing-Satz."""
    state = brain.state
    state.music_love = min(1.0, getattr(state, "music_love", 0.75) + 0.05)
    state.happiness = min(1.0, state.happiness + 0.08)

    if time.time() - state.last_music_reaction < MUSIC_REACTION_GAP_SEC:
        return None
    state.last_music_reaction = time.time()
    state.add_xp(4)
    brain.save()
    sing = pick_sing_line(brain, title)
    if "youtube" in title.lower() and random.random() < 0.4:
        return f"{random.choice(MUSIC_LOVE_LINES)} {sing}"
    return sing


def pick_dance_comment(state: JamState) -> str:
    if state.stage <= 0:
        return "Wipp wipp!"
    return random.choice(DANCE_LINES + MUSIC_LOVE_LINES)


def list_song_picks() -> str:
    """Kurzliste für Menü/Status."""
    return ", ".join(p["title"] for p in SONG_PICKS)
