"""Implementation reasoning (spec Modus 3) — Welt 1 only.

Maps a free-text claim ("Wir haben SBOMs und machen Updates, wenn Kunden Fehler
melden.") onto the product's applicable obligations and reports, per obligation,
whether the *claim* potentially/partially/does-not address it — plus the
evidence that WOULD be needed to prove real implementation.

This is NOT a conformity verdict. It judges the customer's statement, never
whether the obligation is met. The real verdict (ComplianceStatus: erfüllt/
offen/unklar from verified evidence) lives in the Compliance Execution Graph.
The four reasoning layers: claim -> interpretation (capabilities/topics on the
claim) -> potential obligation coverage (`claim_coverage`) -> evidence required.
"""

from __future__ import annotations

from typing import Dict, List

from .claim_normalizer import normalize_claim
from .enums import ClaimCoverage, Confidence
from .obligation_engine import derive_obligations
from .schemas import (
    ClaimObligationMapping,
    CustomerImplementationClaim,
    ImplementationReasoningResponse,
    ProductProfile,
)
from .taxonomy_claims import topics_for

DISCLAIMER = (
    "Diese Auswertung interpretiert ausschließlich die Kundenaussage (ClaimCoverage, Welt 1). "
    "Sie ist KEINE Konformitätsaussage — der tatsächliche Compliance-Status (ComplianceStatus, "
    "Welt 2) ergibt sich erst aus geprüften Nachweisen im Compliance Execution Graph."
)

# Typical sub-elements a capability still misses when only partially claimed.
STANDARD_GAPS: Dict[str, List[str]] = {
    "software_bill_of_materials": [
        "Vulnerability-Monitoring der Komponenten",
        "Bewertung betroffener Komponenten",
        "Lieferantenprozess",
    ],
    "secure_updates": [
        "aktive Schwachstellenüberwachung",
        "Patch-Bewertung",
        "Fristen und Verantwortlichkeiten",
        "Nachweis der Updatefähigkeit",
    ],
    "vulnerability_management": [
        "definierter Vulnerability-Handling-Prozess",
        "Priorisierung und Fristen",
    ],
    "authentication": ["MFA für privilegierte Zugänge", "keine Standard-Zugangsdaten"],
    "security_logging": ["Schutz der Logs vor Manipulation", "Monitoring/Alerting"],
    "software_integrity": ["Signierung der Updates", "Verifikation der Update-Signatur"],
    "secure_by_default": ["Härtung der Auslieferungskonfiguration", "Minimierung der Angriffsfläche"],
    "secure_communication": ["verschlüsselte Übertragung", "Integritätsschutz der Verbindung"],
    "risk_assessment": ["dokumentierte Risikobewertung", "Aufnahme in die technische Doku"],
    "technical_documentation": ["vollständige technische Unterlagen", "Aktualisierung über den Lebenszyklus"],
}


def _missing_for(capabilities: List[str]) -> List[str]:
    out: List[str] = []
    for cap in capabilities:
        for gap in STANDARD_GAPS.get(cap, []):
            if gap not in out:
                out.append(gap)
    return out


def _coverage(required: List[str], claimed: List[str], qualifiers: List[str]) -> ClaimCoverage:
    if not required:
        return ClaimCoverage.INSUFFICIENT_INFORMATION
    req, have = set(required), set(claimed)
    hit = req & have
    if not hit:
        return ClaimCoverage.DOES_NOT_ADDRESS
    if "absent" in qualifiers or "planned" in qualifiers:
        return ClaimCoverage.DOES_NOT_ADDRESS
    if "reactive" in qualifiers and hit & {"secure_updates", "vulnerability_management"}:
        return ClaimCoverage.PARTIALLY_ADDRESSES
    if req <= have:
        return ClaimCoverage.POTENTIALLY_ADDRESSES
    return ClaimCoverage.PARTIALLY_ADDRESSES


def reason_implementation_claim(
    profile: ProductProfile, customer_claim: str
) -> ImplementationReasoningResponse:
    claim = normalize_claim(customer_claim)
    obligations = derive_obligations(profile).applicable_obligations
    claimed = claim.claimed_capability
    claim_topics = set(claim.related_topics) | set(claimed)

    mappings: List[ClaimObligationMapping] = []
    missing_evidence: List[str] = []

    for ob in obligations:
        from .rules_obligations import obligation_rule

        rule = obligation_rule(ob.obligation_id)
        required_caps = rule.required_capabilities if rule else []
        ob_topics = set(topics_for(required_caps)) | set(required_caps)
        directly_claimed = bool(set(required_caps) & set(claimed))
        related = bool(ob_topics & claim_topics)
        if not directly_claimed and not related:
            continue  # unrelated to the claim -> don't reason about it

        coverage = _coverage(required_caps, claimed, claim.qualifiers)
        missing = [] if coverage == ClaimCoverage.POTENTIALLY_ADDRESSES else _missing_for(required_caps)
        if coverage != ClaimCoverage.POTENTIALLY_ADDRESSES:
            for ev in ob.required_evidence:
                if ev not in missing_evidence:
                    missing_evidence.append(ev)
        mappings.append(
            ClaimObligationMapping(
                claim_id=claim.claim_id,
                obligation_id=ob.obligation_id,
                claim_coverage=coverage,
                missing_elements=missing,
                required_evidence=ob.required_evidence,
                explanation=_explain(coverage, ob.title, claim.qualifiers),
                confidence=Confidence.MEDIUM,
            )
        )

    return ImplementationReasoningResponse(
        claim=claim,
        mappings=mappings,
        missing_evidence=missing_evidence,
        summary=_summary(claim, mappings),
        disclaimer=DISCLAIMER,
    )


def _explain(coverage: ClaimCoverage, title: str, qualifiers: List[str]) -> str:
    if coverage == ClaimCoverage.POTENTIALLY_ADDRESSES:
        return "Die Aussage adressiert die Pflicht '%s' direkt — Nachweise erforderlich für eine Bewertung der Umsetzung." % title
    if coverage == ClaimCoverage.PARTIALLY_ADDRESSES:
        extra = " Der beschriebene Prozess wirkt reaktiv." if "reactive" in qualifiers else ""
        return "Die Aussage adressiert die Pflicht '%s' nur teilweise.%s" % (title, extra)
    if coverage == ClaimCoverage.DOES_NOT_ADDRESS:
        return "Die Aussage adressiert die Pflicht '%s' nicht." % title
    return "Zur Pflicht '%s' liegen zu wenige Angaben für eine Einordnung vor." % title


def _summary(claim: CustomerImplementationClaim, mappings: List[ClaimObligationMapping]) -> str:
    if not claim.claimed_capability:
        return "Die Aussage ist zu unspezifisch — bitte konkretisieren, was umgesetzt wurde."
    full = sum(1 for m in mappings if m.claim_coverage == ClaimCoverage.POTENTIALLY_ADDRESSES)
    partial = sum(1 for m in mappings if m.claim_coverage == ClaimCoverage.PARTIALLY_ADDRESSES)
    none = sum(1 for m in mappings if m.claim_coverage == ClaimCoverage.DOES_NOT_ADDRESS)
    return (
        "Die beschriebene Maßnahme adressiert wahrscheinlich %d Pflicht(en) direkt und %d "
        "teilweise; %d werden durch die Aussage nicht berührt. Für eine Bewertung der tatsächlichen "
        "Umsetzung sind Nachweise erforderlich. Dies ist keine Konformitätsaussage." % (full, partial, none)
    )
