"""
session_manager.py  –  Pro-User-Session Verwaltung für v1_upload
Jeder Browser-Tab bekommt eine UUID. Dazu gehört ein eigenes Temp-Verzeichnis
das nach dem Download (oder nach SESSION_TIMEOUT Sekunden) automatisch gelöscht wird.
"""
import logging
import os
import queue
import re
import shutil
import tempfile
import threading
import time
import uuid
from pathlib import Path

# TEMP_BASE kann per Environment überschrieben werden (z.B. unter Windows,
# wo /tmp nicht existiert). Default: Systemtemp-Verzeichnis + Namespace.
TEMP_BASE = Path(os.environ.get("ABRECHNUNG_TEMP_BASE",
                                 str(Path(tempfile.gettempdir()) / "abrechnung_v1")))
SESSION_TIMEOUT = 7200   # 2 Stunden
REAPER_INTERVAL = 300    # alle 5 Minuten aufräumen

_sessions: dict[str, dict] = {}
_lock = threading.Lock()
log   = logging.getLogger("session_manager")

# UUID4-Format – nichts anderes akzeptieren, damit Routen die SID gefahrlos in
# Dateipfade mappen können.
_SID_RE = re.compile(r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$")


def _is_valid_sid(sid) -> bool:
    return isinstance(sid, str) and bool(_SID_RE.match(sid))


# ── Öffentliche API ────────────────────────────────────────────────────────────

def create() -> str:
    """Legt eine neue Session an, gibt die UUID zurück."""
    sid = str(uuid.uuid4())
    td  = TEMP_BASE / sid
    try:
        td.mkdir(parents=True, exist_ok=True)
        (td / "uploads").mkdir(exist_ok=True)
    except OSError:
        log.exception("Temp-Verzeichnis konnte nicht angelegt werden")
        raise
    with _lock:
        _sessions[sid] = {
            "temp_dir":  str(td),
            "born":      time.time(),
            "system":    None,
            "log_queue": queue.Queue(maxsize=5000),
            "status":    "created",
            "data":      {},
        }
    log.info(f"Session erstellt: {sid[:8]}…")
    return sid


def get(sid: str) -> dict | None:
    if not _is_valid_sid(sid):
        return None
    with _lock:
        return _sessions.get(sid)


def update(sid: str, **kwargs):
    """Aktualisiert Felder einer Session. Ignoriert ungültige/unbekannte SIDs."""
    if not _is_valid_sid(sid):
        return
    with _lock:
        s = _sessions.get(sid)
        if s:
            s.update(kwargs)


def destroy(sid: str):
    """Löscht Session + Temp-Verzeichnis. Idempotent – doppelter Aufruf ist OK."""
    if not _is_valid_sid(sid):
        return
    with _lock:
        s = _sessions.pop(sid, None)
    if not s:
        return
    try:
        shutil.rmtree(s["temp_dir"], ignore_errors=True)
    except Exception:
        log.exception(f"Fehler beim Löschen des Temp-Verzeichnisses {s.get('temp_dir')}")
    log.info(f"Session gelöscht: {sid[:8]}…")


def temp_dir(sid: str) -> str | None:
    s = get(sid)
    return s["temp_dir"] if s else None


# ── Hintergrund-Cleanup ───────────────────────────────────────────────────────

def _reaper():
    while True:
        try:
            time.sleep(REAPER_INTERVAL)
            now = time.time()
            with _lock:
                stale = [k for k, v in _sessions.items()
                         if now - v.get("born", now) > SESSION_TIMEOUT]
            for sid in stale:
                log.info(f"Session-Timeout: {sid[:8]}…")
                destroy(sid)
        except Exception:
            # Der Reaper darf niemals sterben – sonst sammeln sich Sessions an.
            log.exception("Fehler im Session-Reaper – Zyklus wird fortgesetzt")


threading.Thread(target=_reaper, daemon=True, name="session-reaper").start()
