#!/usr/bin/env python3
"""P72 v2 — Heuristik nachschaerfen.

v1 hatte 79% 'other'-Bucket. v2 erweitert Patterns deutlich:
  * DSE: deckt allgemeine Art. 13/14 + Betroffenenrechte ab
  * TOM: deckt Art. 32 + technische Sicherheit breit ab
  * cookie_richtlinie: alle Tracking/Analyse-Patterns
  * process: alle Schulung/Verfahren/Meldepflicht-Patterns

Re-classify NUR den 'other'-Bucket — die anderen Buckets aus v1 sind bereits
spezifisch genug, die wollen wir nicht nochmal anfassen.
"""
from __future__ import annotations

import os
import re
import sys
from typing import Pattern

import psycopg2

# Patterns v2: deutlich lockerer (vorher zu strict, 79% landeten in 'other')
RULES_V2: list[tuple[str, Pattern]] = [
    # Banner-UI (sehr eng — bleibt v1)
    (
        "banner_implementation",
        re.compile(
            r"\b(banner|cookie[-\s]?wall|pre[-\s]?ticked|"
            r"vorausgewaehlt(e|en)?\s*checkbox|"
            r"browser[-\s]?(default|standard|einstellung).{0,30}einwilligung|"
            r"dark[-\s]?pattern|reject.{0,20}button|ablehn.{0,20}button|"
            r"floating.{0,20}icon|cookie[-\s]?einstellungen)"
        ),
    ),
    # CMP-Audit
    (
        "cmp_audit",
        re.compile(
            r"\b(consent[-\s]?(log|trail|audit)|"
            r"einwilligungs(nachweis|log|trail|protokoll)|"
            r"datensaetze?.{0,30}einwilligung|"
            r"zeitstempel.{0,30}einwilligung)"
        ),
    ),
    # AVV (Art. 28)
    (
        "avv",
        re.compile(
            r"\b(art\.?\s*28|auftragsverarbeit|adv|avv|"
            r"data[-\s]?processing[-\s]?agreement|dpa|"
            r"weisungsgebunden(er)?\s*auftragsverarbeit)"
        ),
    ),
    # JC (Art. 26)
    (
        "jc",
        re.compile(
            r"\b(art\.?\s*26|joint[-\s]?controller|"
            r"gemeinsam(e|er)\s*verantwortlich|"
            r"konzern.{0,40}(verantwortlich|verarbeit)|"
            r"gemeinsame.{0,20}verarbeitung)"
        ),
    ),
    # Impressum
    (
        "impressum",
        re.compile(
            r"\b((paragraph|§)\s*5\s*(tmg|ddg)|"
            r"§\s*18\s*mstv|"
            r"impressum|anbieterkennzeichnung|"
            r"geschaeftsbrief|firma.{0,20}kaufmann|"
            r"vollstaendige.{0,20}geschaeftsadresse|"
            r"postalische?.{0,30}adresse|"
            r"handelsregister.{0,30}eintrag|"
            r"§\s*55\s*rstv)"
        ),
    ),
    # AGB
    (
        "agb",
        re.compile(
            r"\b(agb|allgemeine\s*geschaeftsbedingungen|"
            r"vertragsbedingungen|"
            r"§\s*305.{0,5}(bgb)?|"
            r"klausel.{0,30}wirksam|"
            r"vertragsabschluss.{0,40}online)"
        ),
    ),
    # Widerruf
    (
        "widerruf",
        re.compile(
            r"\b(widerrufsbelehrung|widerrufsrecht|"
            r"14.{0,10}tage.{0,10}frist|"
            r"musterwiderruf|"
            r"§\s*355\s*bgb)"
        ),
    ),
    # Accounting
    (
        "accounting",
        re.compile(
            r"\b((rechnung|invoice).{0,30}(angeben|enthalten|fuehren)|"
            r"§\s*14\s*ustg|umsatzsteueridentifikation\s+nummer.{0,30}rechnung|"
            r"buchhaltung|"
            r"steuernummer.{0,30}rechnung)"
        ),
    ),
    # Cookie-Richtlinie (erweitert)
    (
        "cookie_richtlinie",
        re.compile(
            r"\b(cookie[-\s]?(richtlinie|policy|liste|tabelle|verzeichnis)|"
            r"§\s*25\s*(tddg|tdddg|ttdsg)|"
            r"§\s*165.{0,5}tkg|"
            r"tracking[-\s]?technologi|"
            r"tracking[-\s]?(pixel|skript)|"
            r"webstorage|local[-\s]?storage.{0,30}einwilligung|"
            r"cookie.{0,30}(zweck|speicherdauer|drittland|anbieter)|"
            r"google\s+analytics|matomo|piwik|hotjar|"
            r"facebook\s+pixel|meta\s+pixel|google\s+tag\s+manager)"
        ),
    ),
    # TOM (deutlich erweitert)
    (
        "tom",
        re.compile(
            r"\b(art\.?\s*32|"
            r"verschluesselung|encryption|kryptograph|"
            r"backup|wiederherstell|recovery|"
            r"pseudonymisier|anonymisier|"
            r"zugriffskontrolle|berechtigungskonzept|"
            r"benutzerverwaltung|identity[-\s]?management|"
            r"penetrationstest|security[-\s]?incident|"
            r"intrusion[-\s]?detection|firewall|"
            r"tom|technisch[-\s]?organisatorische|"
            r"iso[-\s]?2700[12]|bsi[-\s]?grundschutz|"
            r"protokollier(ung)?|audit[-\s]?log|"
            r"datensicherheit|netzwerksicherheit|"
            r"patch[-\s]?management|update[-\s]?prozess|"
            r"physische?\s+sicherheit|zutrittskontrolle)"
        ),
    ),
    # DSE (DEUTLICH erweitert — wichtigster Bucket fuer aktuelle Audits)
    (
        "dse",
        re.compile(
            r"\b(art\.?\s*1[34]|"
            r"datenschutzerklaerung|datenschutzhinweis|datenschutzinformation|"
            r"informationspflicht|"
            r"empfaenger(\s*oder\s*empfaengerkategorien)?|"
            r"drittland.{0,30}(transfer|uebermittlung)|"
            r"verantwortlich(er|en)\s+benennen|"
            r"rechtsgrundlage.{0,30}(verarbeitung|nennen|angeben)|"
            r"betroffenenrecht|"
            r"art\.?\s*1[5-9]\s*dsgvo|art\.?\s*2[0-2]\s*dsgvo|"
            r"art\.?\s*1[5-9]|art\.?\s*2[0-2]|"
            r"auskunftsrecht|berichtigungsrecht|loeschungsrecht|"
            r"einschraenkungsrecht|datenportabilitaet|widerspruchsrecht|"
            r"einwilligung.{0,40}widerruf|"
            r"datenschutzbeauftragt(er|en)?|dsb|"
            r"aufsichtsbehoerde.{0,30}(benennen|nennen|kontakt)|"
            r"beschwerderecht|art\.?\s*77|"
            r"speicherdauer|loeschfrist|aufbewahrungsfrist|"
            r"besondere\s+kategorien|art\.?\s*9\s*dsgvo|"
            r"profiling|automatisierte\s+entscheidung|art\.?\s*22)"
        ),
    ),
    # Process (erweitert)
    (
        "process",
        re.compile(
            r"\b(prozess|verfahren|workflow|"
            r"durchfuehren|umsetzen|implementieren|"
            r"schulung|mitarbeiterunterweis|sensibilisier|"
            r"regelmaessig.{0,30}pruefen|"
            r"kontinuierlich|laufend|fortlaufend|"
            r"datenpannenmeldung|art\.?\s*3[34]|"
            r"meldepflicht.{0,30}(behoerd|datenpannen|breach)|"
            r"loeschkonzept|loeschroutine|"
            r"hinweisgebersystem|whistleblow|hinschg|"
            r"dsfa|datenschutz[-\s]?folgenabschaetz|art\.?\s*35|"
            r"vvt|verzeichnis.{0,30}verarbeitung|art\.?\s*30|"
            r"risiko(analyse|bewertung|management)|"
            r"penetration.{0,30}durchfuehr|"
            r"audit.{0,30}durchfuehr|"
            r"kontrolle.{0,30}(durchfuehr|etablier)|"
            r"nachweis(fuehrung|pflicht)|rechenschaft)"
        ),
    ),
]


def classify_v2(title: str, objective: str, tags: str | None = None) -> str:
    text = " ".join(
        s.lower() for s in (title or "", objective or "", tags or "") if s
    )
    for scope, pattern in RULES_V2:
        if pattern.search(text):
            return scope
    return "other"


def main() -> int:
    dsn = os.environ.get("DATABASE_URL")
    if not dsn:
        print("DATABASE_URL missing", file=sys.stderr)
        return 1
    conn = psycopg2.connect(dsn)
    cur = conn.cursor()

    # NUR den 'other'-Bucket aus v1 re-classifizieren
    cur.execute("""
        SELECT id, title, objective, tags
        FROM compliance.canonical_controls
        WHERE scope_doc_type = 'other'
        AND merged_into_uuid IS NULL
    """)
    rows = cur.fetchall()
    print(f"Re-classifying {len(rows):,} 'other'-bucket MCs with v2 heuristic...",
          file=sys.stderr)

    from collections import Counter
    stats = Counter()
    moved = 0
    batch = []
    for row_id, title, objective, tags in rows:
        new_scope = classify_v2(title or "", objective or "", tags)
        if new_scope != "other":
            moved += 1
        stats[new_scope] += 1
        batch.append((new_scope, row_id))
        if len(batch) >= 1000:
            cur.executemany(
                "UPDATE compliance.canonical_controls SET scope_doc_type=%s WHERE id=%s",
                batch,
            )
            conn.commit()
            print(f"  re-classified {sum(stats.values()):,} so far, moved={moved:,}",
                  file=sys.stderr)
            batch = []
    if batch:
        cur.executemany(
            "UPDATE compliance.canonical_controls SET scope_doc_type=%s WHERE id=%s",
            batch,
        )
        conn.commit()

    print(f"\n=== v2 Re-classification: moved {moved:,} / {len(rows):,} from 'other' ===")
    for scope, n in sorted(stats.items(), key=lambda x: -x[1]):
        pct = 100 * n / max(1, len(rows))
        print(f"  {scope:25s} {n:>7,} ({pct:>4.1f}%)")
    return 0


if __name__ == "__main__":
    sys.exit(main())
