"""DSE Shadow-Verdrahtung der Obligation Aggregation Engine.

Erzeugt aus den v3-`results` zusätzlich Obligation-Ergebnisse — AUSSCHLIESSLICH
für die Telemetrie (Shadow Mode). Ändert KEINE nutzer-sichtbaren Findings.

Mapping control-level über generation_metadata.legal_obligations +
applicability.conditional; das `met`-Signal ist das Legacy-`passed` des Controls
(kein zusätzlicher Prüfer-Call, kein Key). Liefert die Vergleichszahlen, mit denen
sich der Umschalt-Entscheid später absichern lässt:
  legacy_control_findings · obligation_shadow_results · collapse_factor ·
  na_count · met_failed_delta · top_collapsed_obligations
"""
from __future__ import annotations

import logging
import os
from typing import Any, Optional

logger = logging.getLogger(__name__)


async def fetch_obligation_markers(cids: list[str], db_url: str = "") -> dict[str, dict]:
    """legal_obligations + applicability.conditional der Controls laden.
    Leeres Dict bei Fehler/keiner DB (Shadow fällt still aus)."""
    cids = [c for c in cids if c]
    if not cids:
        return {}
    import json
    dsn = db_url or os.getenv("DATABASE_URL") or os.getenv("COMPLIANCE_DATABASE_URL")
    if not dsn:
        return {}
    try:
        import asyncpg
        conn = await asyncpg.connect(dsn)
        rows = await conn.fetch(
            "select control_id, generation_metadata->'legal_obligations' obl, "
            "generation_metadata->'applicability'->>'conditional' cond "
            "from compliance.canonical_controls "
            "where control_id = any($1::text[]) "
            "and generation_metadata ? 'legal_obligations'", cids)
        await conn.close()
    except Exception as e:
        logger.warning("fetch_obligation_markers failed: %s", e)
        return {}
    out: dict[str, dict] = {}
    for r in rows:
        obl = r["obl"]
        obl = json.loads(obl) if isinstance(obl, str) else obl
        if obl:
            out[r["control_id"]] = {"obl": obl, "cond": r["cond"]}
    return out


def compute_obligation_shadow(results: list[dict], text: str,
                              markers: dict[str, dict]) -> dict[str, Any]:
    """Reiner Shadow-Vergleich (keine DB, keine Seiteneffekte). `markers`:
    {control_id: {obl:[...], cond:str|None}}. `met` = Legacy-`passed`."""
    from compliance.services.obligation_aggregation import (
        FAILED, LM, MET, NA, PARTIAL, CriterionEval, aggregate_obligations,
    )
    from compliance.services.obligation_applicability import applicable
    from compliance.services.obligation_taxonomy import requires_llm

    legacy = 0
    evals: list[Any] = []
    contrib: dict[str, list] = {}
    for r in results:
        cid = r.get("control_id")
        m = markers.get(cid)
        if not m:
            continue
        passed = bool(r.get("passed"))
        if not passed:
            legacy += 1
        for ob in m["obl"]:
            evals.append(CriterionEval(ob, LM, passed, cid, "", "", m.get("cond")))
            contrib.setdefault(ob, []).append((cid, passed))
    if not evals:
        return {"status": "no obligation markers on result controls"}

    obls = aggregate_obligations(evals, applicable_fn=applicable, doc_text=text)
    # FAILED/PARTIAL ehrlich trennen: echte Lücke (failed_by_current_checker) vs
    # RECALL_LIMITED (Obligation braucht LLM, aktueller Prüfer kann sie nicht verifizieren).
    findings = failed_current = recall_limited = na = 0
    for o in obls:
        if o.status == NA:
            na += 1
        elif o.status in (FAILED, PARTIAL):
            findings += 1
            if requires_llm(o.obligation_id):
                recall_limited += 1
            else:
                failed_current += 1
    top = []
    for o in obls:
        cs = contrib.get(o.obligation_id, [])
        fehlt = sum(1 for _, p in cs if not p)
        if fehlt >= 2:
            top.append({"obligation": o.obligation_id, "fehlt": fehlt,
                        "total": len(cs), "status": o.status,
                        "recall_limited": bool(requires_llm(o.obligation_id)
                                               and o.status in (FAILED, PARTIAL))})
    top.sort(key=lambda x: -x["fehlt"])
    met_count = sum(1 for o in obls if o.status == MET)
    recall_limited_obls = sorted({o.obligation_id for o in obls
                                  if o.status in (FAILED, PARTIAL)
                                  and requires_llm(o.obligation_id)})
    return {
        "legacy_control_findings": legacy,
        "obligation_shadow_results": len(obls),
        "obligation_findings": findings,
        "failed_by_current_checker": failed_current,
        "recall_limited": recall_limited,
        "met_count": met_count,
        "collapse_factor": round(legacy / findings, 2) if findings else None,
        "na_count": na,
        "met_failed_delta": legacy - findings,
        "top_collapsed_obligations": top[:10],
        "recall_limited_obligations": recall_limited_obls,
    }


async def build_obligation_shadow(results: list[dict], text: str,
                                  db_url: str = "") -> dict[str, Any]:
    """Async-Wrapper: Marker laden, dann Shadow rechnen. NIE in `results` schreiben."""
    cids = [r.get("control_id") for r in results if r.get("control_id")]
    markers = await fetch_obligation_markers(cids, db_url)
    if not markers:
        return {"status": "no markers"}
    return compute_obligation_shadow(results, text, markers)
