"""DSE-Tiefenprüfung: LLM-Kaskade auf die UNSCHARFEN Findings.

User-Architektur (2026-06-18): die deterministische Engine (Keyword + Embedding)
triagiert. Eindeutige Fälle (sehr hoher/niedriger Embedding-Score) bleiben
deterministisch. Die UNSCHARFE Mitte + grenzwertig-Bestandene gehen durch die
Kaskade — denn dort entstehen sowohl 'verpasste Lücken' (schlimmster Fehler) als
auch Falsch-Findings (Rework).

Eskalation auf ANTWORT-UNSICHERHEIT (nicht JSON-Gültigkeit): jedes Tier liefert
{erfuellt, confidence, begruendung}. Confidence < Schwelle → nächstes Tier.
  Tier 1: Qwen 35B (lokal, schnell, billig)
  Tier 2: OVH gpt-oss-120B
  Tier 3: Claude — NUR mit Freigabe (allow_claude), sonst 'needs_freigabe'.

Judging-Leitplanken (User-Vorgaben):
  - Speicherdauer nur erfüllt bei konkreter Höchstdauer ODER echtem,
    nachvollziehbarem Kriterium — NICHT zirkulär ('bis Zweck wegfällt').
  - Ohne ausreichenden Kontext → eher nicht erfüllt (nichts fehlen lassen).
"""

from __future__ import annotations

import json
import logging
import os

from compliance.services.llm_cascade import (
    _call_anthropic, _call_ollama, _call_ovh,
)

logger = logging.getLogger(__name__)

# Unscharfe Embedding-Zone (kalibriert an 5-Firmen-GT 2026-06-18): außerhalb ist
# die Engine sicher genug, innen entscheidet der LLM.
FUZZY_LO = float(os.getenv("DSE_FUZZY_LO", "0.50"))
FUZZY_HI = float(os.getenv("DSE_FUZZY_HI", "0.72"))
# Selbstkonfidenz-Schwelle: darunter → eskalieren.
ESC_CONF = float(os.getenv("DSE_ESC_CONF", "0.75"))

_JUDGE_SYS = (
    "Du bist ein erfahrener DSGVO-Datenschutz-Auditor. Du prüfst, ob eine "
    "konkrete Pflicht in einer Datenschutzerklärung (DSE) ERFÜLLT ist. "
    "Sei streng wie ein Fachanwalt: lieber 'nicht erfüllt' wenn unklar — eine "
    "übersehene Lücke ist schlimmer als ein Hinweis zu viel. "
    "Speicherdauer ist NUR erfüllt bei konkreter Höchstdauer ODER einem echten, "
    "nachvollziehbaren Kriterium; zirkuläre Formeln ('bis der Zweck wegfällt') "
    "erfüllen die Pflicht NICHT. "
    'Antworte AUSSCHLIESSLICH als JSON: '
    '{"erfuellt": true|false, "confidence": 0.0-1.0, "begruendung": "kurz"}'
)


def _build_user(doc_text: str, title: str, criteria: list) -> str:
    crit = "; ".join(str(c) for c in (criteria or []) if c)[:600]
    return (
        f"PFLICHT: {title}\n"
        f"Erfüllt, wenn: {crit}\n\n"
        f"DATENSCHUTZERKLÄRUNG (Auszug):\n{doc_text[:14000]}\n\n"
        "Ist die Pflicht im Text inhaltlich erfüllt?"
    )


def _parse(text: str) -> dict | None:
    if not text:
        return None
    s, e = text.find("{"), text.rfind("}")
    if s < 0 or e <= s:
        return None
    try:
        o = json.loads(text[s:e + 1])
        return {
            "erfuellt": bool(o.get("erfuellt")),
            "confidence": float(o.get("confidence") or 0.0),
            "begruendung": str(o.get("begruendung") or "")[:300],
        }
    except Exception:
        return None


async def judge_control(
    doc_text: str, title: str, criteria: list, allow_claude: bool = False,
) -> dict:
    """Tiered judgment mit Selbstkonfidenz-Eskalation. Returns
    {erfuellt, confidence, source, begruendung, needs_freigabe}."""
    user = _build_user(doc_text, title, criteria)
    tiers = [("qwen", _call_ollama), ("ovh_120b", _call_ovh)]
    best: dict | None = None
    for name, call in tiers:
        try:
            if name == "qwen":
                txt = await call(_JUDGE_SYS, user, max_tokens=400,
                                 timeout=60, think=False)
            else:
                txt = await call(_JUDGE_SYS, user, max_tokens=400)
        except Exception as e:
            logger.warning("deep_check tier %s failed: %s", name, e)
            txt = ""
        p = _parse(txt)
        if p:
            p["source"] = name
            best = p
            if p["confidence"] >= ESC_CONF:
                return {**p, "needs_freigabe": False}
    # Tier 3: Claude — nur mit Freigabe
    if not allow_claude:
        if best:
            return {**best, "needs_freigabe": True}
        return {"erfuellt": False, "confidence": 0.0, "source": "none",
                "begruendung": "Unsicher — Anwalt/Claude-Freigabe nötig",
                "needs_freigabe": True}
    try:
        txt = await _call_anthropic(_JUDGE_SYS, user, max_tokens=400)
        p = _parse(txt)
        if p:
            return {**p, "source": "anthropic_claude", "needs_freigabe": False}
    except Exception as e:
        logger.warning("deep_check claude failed: %s", e)
    if best:
        return {**best, "needs_freigabe": False}
    return {"erfuellt": False, "confidence": 0.0, "source": "none",
            "begruendung": "Kein LLM-Ergebnis", "needs_freigabe": False}


def is_fuzzy(score: float, kw_pass: bool) -> bool:
    """Unscharf = im Embedding-Graubereich UND nicht durch Keyword klar bestätigt.
    Klar-bestanden (kw) bleibt deterministisch; klar-hoch/niedrig auch."""
    if kw_pass:
        return False
    return FUZZY_LO <= score <= FUZZY_HI
