"""Storage-Inventory — trennt echte Cookies von anderem Endgeräte-Speicher.

Viele CMPs/Scanner werfen Cookies + Local/Session Storage + IndexedDB +
Framework-Artefakte in EINE „Cookie"-Liste und erfinden Laufzeiten dazu. § 25
TDDDG ist aber technologieneutral (alle Endgeräte-Speicher gleich). Dieses
Modul klassifiziert heuristisch (Name-Muster + Laufzeit-Text) und liefert das
Inventar + einen Transparenz-Befund. v1 ohne Scanner-Umbau; echte Erfassung
(localStorage/IndexedDB/SW) folgt im consent-tester (v2).
"""

from __future__ import annotations

import re

# Salesforce-Lightning/Aura- + typische Framework-/LocalStorage-Artefakte.
_FRAMEWORK_RE = re.compile(
    r"componentdefstorage|globalvalueproviders|__mutex|\blskey|\$vfrc",
    re.IGNORECASE,
)
_SESSION_HINT = ("session storage", "sessionstorage")
_INDEXEDDB_HINT = ("indexeddb", "indexed db")
_LOCAL_HINT = ("local storage", "localstorage")
# „kein Ablauf"-Formulierungen → Persistenzspeicher (kein echtes Cookie).
_PERSIST_NOEXPIRY = (
    "bis es durch den nutzer", "deaktiviert wird", "bis zur löschung",
    "bis zur loeschung", "vom nutzer gelöscht", "vom nutzer geloescht",
)

STORAGE_LABELS = {
    "cookie": "Cookie",
    "local_storage": "Local Storage",
    "session_storage": "Session Storage",
    "indexeddb": "IndexedDB",
    "framework_storage": "Framework-Storage",
}


def dedupe_vendor_cookies(vendors: list[dict]) -> list[dict]:
    """Cookies tauchen je Vendor mehrfach auf (Consent-Phasen before_consent /
    after_accept / after_reject derselben Crawl-Session). Dedupliziert je Vendor
    nach (lower) Name — behält den ersten. Behebt aufgeblähte Cookie-/Finding-
    Zahlen (BMW: 2196 → ~772 eindeutig)."""
    out: list[dict] = []
    for v in vendors or []:
        seen: set[str] = set()
        uniq: list[dict] = []
        for c in (v.get("cookies") or []):
            n = (c.get("name") or "").strip().lower()
            if n and n in seen:
                continue
            if n:
                seen.add(n)
            uniq.append(c)
        nv = dict(v)
        nv["cookies"] = uniq
        out.append(nv)
    return out


def detect_storage_type(name: str, expiry: str = "") -> str:
    """Heuristik: echtes Cookie vs. anderer Endgeräte-Speicher.

    Konservativ — im Zweifel 'cookie'. Ist eine VERMUTUNG (kein Scanner-Beleg).
    """
    n = (name or "").strip()
    e = (expiry or "").lower()
    if _FRAMEWORK_RE.search(n):
        return "framework_storage"
    if any(h in e for h in _SESSION_HINT):
        return "session_storage"
    if any(h in e for h in _INDEXEDDB_HINT):
        return "indexeddb"
    if any(h in e for h in _LOCAL_HINT):
        return "local_storage"
    if any(h in e for h in _PERSIST_NOEXPIRY):
        return "local_storage"
    return "cookie"


def build_storage_inventory(vendors: list[dict]) -> dict:
    """Zählt je Speichertyp + liefert Beispiele für Nicht-Cookies."""
    by_type: dict[str, int] = {}
    examples: list[dict] = []
    per_cookie: dict[str, str] = {}
    for v in vendors or []:
        vname = v.get("name") or "?"
        for c in v.get("cookies") or []:
            st = detect_storage_type(c.get("name", ""), c.get("expiry", ""))
            by_type[st] = by_type.get(st, 0) + 1
            n = (c.get("name") or "").lower()
            if n:
                per_cookie[n] = st
            if st != "cookie" and len(examples) < 10:
                examples.append({
                    "name": c.get("name", ""), "type": st, "vendor": vname,
                })
    total = sum(by_type.values())
    cookies = by_type.get("cookie", 0)
    return {
        "total": total,
        "by_type": by_type,
        "real_cookies": cookies,
        "other_storage": total - cookies,
        "examples": examples,
        # name_lower → Speichertyp (für den Frontend-Filter).
        "per_cookie": per_cookie,
    }


def storage_transparency_finding(inv: dict) -> dict | None:
    """Ein Summen-Befund, wenn Nicht-Cookies als Cookies gelistet sind."""
    other = inv.get("other_storage", 0)
    if other <= 0:
        return None
    by = inv.get("by_type", {})
    parts = ", ".join(
        f"{by[k]} {STORAGE_LABELS.get(k, k)}" for k in by if k != "cookie"
    )
    return {
        "vendor": "—",
        "cookie": f"{other} Objekte",
        "type": "storage_transparency",
        "severity": "MEDIUM",
        "declared": f"{inv['total']} als Cookies gelistet",
        "library_purpose": f"vermutlich: {parts}",
        "remediation": (
            f"{other} von {inv['total']} als 'Cookie' gelisteten Objekten sind "
            f"vermutlich anderer Endgeräte-Speicher ({parts}). § 25 TDDDG ist "
            f"technologieneutral — Speichertechnologie + -dauer pro Objekt "
            f"transparent darstellen (echtes Cookie vs. Local Storage / Framework)."
        ),
        "control": {
            "control_id": "DATA-2851-A05",
            "regulation": "TDDDG", "article": "§ 25 Abs. 1",
        },
    }
