"""
Beobachtet, was David am PC macht, und speichert es in Jams Gedächtnis.

Nur Fenstertitel + Programmname (keine Tastatureingaben, keine Screenshots).
Sensible Fenster (Passwort, Banking, …) werden übersprungen.
"""

from __future__ import annotations

import ctypes
import re
import time
from ctypes import wintypes
from pathlib import Path

from jam.brain import JamBrain, JamState
from jam.game_watcher import foreground_title, looks_like_gaming

ROOT = Path(__file__).resolve().parent.parent
ACTIVITY_LOG_FILE = ROOT / "data" / "pc-activity-log.json"

TICK_MIN_SEC = 8.0
SESSION_MIN_SEC = 20.0

SKIP_TITLE = re.compile(
    r"(passwort|password|pin\b|banking|bank\s|kreditkarte|seed\s|mnemonic|"
    r"private\s*key|2fa|authenticator|wallet\s*backup)",
    re.I,
)

CATEGORY_RULES: list[tuple[str, re.Pattern]] = [
    ("torchwood_mod", re.compile(
        r"(torchwood|torch\s*wood|torchwood\.app)",
        re.I,
    )),
    ("coding", re.compile(
        r"(cursor|visual studio|vscode|code\.exe|pycharm|intellij|github|"
        r"powershell|terminal|windows terminal|jam-buddy|python|\.py\b|"
        r"node\.js|webpack|docker|git\b)",
        re.I,
    )),
    ("gaming", re.compile(
        r"(steam|fortnite|apex|minecraft|valorant|epic games|battle\.net|"
        r"roblox|genshin|overwatch|dota|csgo|counter-strike|gog galaxy|"
        r"ubisoft|rockstar|xbox|playstation|game\b|spiel)",
        re.I,
    )),
    ("shop", re.compile(
        r"(fiverr|paypal|shop|verkauf|ki-web-studio|gumroad|etsy|stripe|"
        r"jam-shop|8765|netlify)",
        re.I,
    )),
    ("mining", re.compile(
        r"(lolminer|nicehash|unmineable|gpu mining|miner\b|etchash)",
        re.I,
    )),
    ("discord", re.compile(r"(discord|slack|teams\b)", re.I)),
    ("music", re.compile(
        r"(spotify|youtube music|soundcloud|vlc|foobar|winamp|\.mp3|\.flac)",
        re.I,
    )),
    ("browser", re.compile(
        r"(chrome|firefox|edge|brave|opera|safari|browser)",
        re.I,
    )),
    ("video", re.compile(r"(youtube|twitch|netflix|prime video|disney\+)", re.I)),
    ("office", re.compile(
        r"(excel|word|powerpoint|outlook|notion|obsidian|pdf|acrobat)",
        re.I,
    )),
]

CATEGORY_LABELS = {
    "torchwood_mod": "Torchwood Chat-Moderation",
    "coding": "Programmieren",
    "gaming": "Zocken",
    "shop": "Shop/Verkauf",
    "mining": "Mining",
    "discord": "Chat",
    "music": "Musik",
    "browser": "Internet",
    "video": "Video",
    "office": "Büro/Dokumente",
    "other": "am PC",
}


def is_enabled(state: JamState) -> bool:
    return bool(getattr(state, "jam_pc_activity_learn", True))


def enable_learn(brain: JamBrain) -> str:
    brain.state.jam_pc_activity_learn = True
    brain.state.remember("PC-Aktivität lernen: AN")
    brain.save()
    return "Okay David – ich merke mir, was du am PC machst (Fenster & Apps)."


def disable_learn(brain: JamBrain) -> str:
    brain.state.jam_pc_activity_learn = False
    brain.save()
    return "PC-Aktivität lernen ist aus – ich beobachte nicht mehr."


def foreground_process_name() -> str:
    try:
        user32 = ctypes.windll.user32
        kernel32 = ctypes.windll.kernel32
        hwnd = user32.GetForegroundWindow()
        if not hwnd:
            return ""
        pid = wintypes.DWORD()
        user32.GetWindowThreadProcessId(hwnd, ctypes.byref(pid))
        handle = kernel32.OpenProcess(0x1000, False, pid.value)
        if not handle:
            return ""
        buf = ctypes.create_unicode_buffer(520)
        size = wintypes.DWORD(520)
        ok = kernel32.QueryFullProcessImageNameW(
            handle, 0, buf, ctypes.byref(size)
        )
        kernel32.CloseHandle(handle)
        if ok:
            return Path(buf.value).stem.lower()
    except (OSError, AttributeError):
        pass
    return ""


def _sanitize_title(title: str) -> str:
    t = re.sub(r"\s+", " ", (title or "").strip())
    if len(t) > 120:
        t = t[:117] + "…"
    return t


def _title_key(title: str, exe: str) -> str:
    t = _sanitize_title(title).lower()
    if exe:
        return f"{exe}|{t[:60]}"
    return t[:80] or "desktop"


def classify_activity(title: str, exe: str = "") -> str:
    from jam.torchwood_moderator import classify_torchwood

    blob = f"{title} {exe}"
    tw = classify_torchwood(title, exe)
    if tw:
        return tw
    if looks_like_gaming(title):
        return "gaming"
    for cat, pat in CATEGORY_RULES:
        if pat.search(blob):
            return cat
    return "other"


def _session(state: JamState) -> dict:
    s = getattr(state, "pc_learn_session", None)
    return s if isinstance(s, dict) else {}


def _set_session(
    state: JamState,
    *,
    category: str,
    title_key: str,
    title: str,
    exe: str,
) -> None:
    now = time.time()
    state.pc_learn_session = {
        "category": category,
        "title_key": title_key,
        "title": _sanitize_title(title),
        "exe": exe,
        "started_at": now,
        "last_seen": now,
    }


def _flush_session(state: JamState, brain: JamBrain | None) -> None:
    sess = _session(state)
    if not sess:
        return
    started = float(sess.get("started_at", 0))
    dur = max(0.0, time.time() - started)
    if dur < SESSION_MIN_SEC:
        state.pc_learn_session = {}
        return

    cat = str(sess.get("category", "other"))
    title = str(sess.get("title", ""))[:100]
    label = CATEGORY_LABELS.get(cat, cat)

    totals = dict(getattr(state, "pc_activity_totals", {}) or {})
    totals[cat] = round(float(totals.get(cat, 0)) + dur, 1)
    state.pc_activity_totals = totals

    entry = {
        "at": time.time(),
        "category": cat,
        "seconds": round(dur, 1),
        "title": title,
        "exe": sess.get("exe", ""),
    }
    log = list(getattr(state, "pc_activity_log", []) or [])
    log.append(entry)
    state.pc_activity_log = log[-400:]
    state.pc_activity_events = getattr(state, "pc_activity_events", 0) + 1
    state.last_pc_activity_learn_at = time.time()

    note = f"David {int(dur)}s {label}: {title[:70]}"
    state.remember(note)
    state.add_xp(min(25, 3 + int(dur // 60)))

    for word in re.findall(r"[A-Za-zÄÖÜäöüß]{5,}", title)[:5]:
        state.learn_word(word.lower())
    if cat == "coding":
        for w in ("cursor", "python", "programmieren", "code"):
            state.learn_word(w)
    elif cat == "gaming":
        state.learn_word("zocken")
    elif cat == "shop":
        state.learn_word("verkaufen")
    elif cat == "torchwood_mod":
        from jam.torchwood_moderator import on_session_learn

        on_session_learn(state, seconds=dur, title=title)
        state.torchwood_announced = False

    interests = dict(getattr(state, "pc_activity_interests", {}) or {})
    interests[cat] = round(float(interests.get(cat, 0)) + dur / 3600.0, 3)
    state.pc_activity_interests = interests

    if brain:
        brain.save()
    state.pc_learn_session = {}


def tick(brain: JamBrain) -> str | None:
    """Ein Poll – bei Sitzungswechsel wird gelernt."""
    state = brain.state
    if not is_enabled(state):
        return None

    last_tick = float(getattr(state, "last_pc_activity_tick_at", 0))
    now = time.time()
    if now - last_tick < TICK_MIN_SEC:
        return None
    state.last_pc_activity_tick_at = now

    title = foreground_title()
    exe = foreground_process_name()
    if not title and not exe:
        return None
    if title and SKIP_TITLE.search(title):
        return None

    category = classify_activity(title, exe)
    key = _title_key(title, exe)

    if category == "torchwood_mod":
        from jam.torchwood_moderator import is_torchwood_title, poll_chat_from_file

        poll_chat_from_file(brain)
        if is_torchwood_title(title) and not getattr(state, "torchwood_announced", False):
            state.torchwood_announced = True
            state.remember("David moderiert auf Torchwood – ich lese den Chat mit.")
    sess = _session(state)

    if not sess:
        _set_session(state, category=category, title_key=key, title=title, exe=exe)
        brain.save()
        return None

    same = (
        sess.get("category") == category
        and sess.get("title_key") == key
    )
    if same:
        sess["last_seen"] = now
        state.pc_learn_session = sess
        return None

    _flush_session(state, brain)
    _set_session(state, category=category, title_key=key, title=title, exe=exe)
    brain.save()
    return None


def summary_for_owner(state: JamState, *, hours: float = 24.0) -> str:
    totals = dict(getattr(state, "pc_activity_totals", {}) or {})
    if not totals:
        return "Ich hab noch keine PC-Aktivität gespeichert."
    items = sorted(totals.items(), key=lambda x: -x[1])[:6]
    parts = [
        f"{CATEGORY_LABELS.get(k, k)}: {int(v // 60)} Min"
        if v >= 60
        else f"{CATEGORY_LABELS.get(k, k)}: {int(v)} s"
        for k, v in items
    ]
    ev = getattr(state, "pc_activity_events", 0)
    return f"PC-Lernen ({int(hours)}h+): " + ", ".join(parts) + f" ({ev} Wechsel)."


def hears_pc_learn(text: str) -> bool:
    h = text.lower().replace("jam", " ")
    if any(w in h for w in ("aus", "stop", "stopp", "nicht mehr")) and any(
        w in h for w in ("pc lernen", "aktivität", "aktivitaet", "beobacht")
    ):
        return True
    return any(
        w in h
        for w in (
            "pc lernen",
            "alles lernen",
            "aktivität lernen",
            "aktivitaet lernen",
            "was mache ich",
            "was mach ich",
            "pc aktivität",
            "pc aktivitaet",
            "beobachte",
            "merk dir was",
        )
    )


def handle_speech(brain: JamBrain, heard: str) -> str | None:
    if not hears_pc_learn(heard):
        return None
    h = heard.lower()
    if any(w in h for w in ("aus", "stop", "stopp", "nicht mehr", "deaktiv")):
        return disable_learn(brain)
    if any(w in h for w in ("an", "ein", "start", "alles")):
        return enable_learn(brain)
    if any(w in h for w in ("zusammenfassung", "übersicht", "uebersicht", "was hab", "was lern")):
        return summary_for_owner(brain.state)
    return enable_learn(brain)
