"""4-Status-Modell: Applicability ≠ Compliance, Unknown ≠ Fail.

User-Datenmodell 2026-06-10:
  - Rechtsform-abhaengige Pflicht (Handelsregister/Vertretungsberechtigte)
    bei UNBESTIMMTER Rechtsform → INSUFFICIENT_EVIDENCE (severity INFO),
    NICHT hartes FAIL ('Muster Consulting' ohne Rechtsform).
  - Rechtsform im Text ODER im Wizard-Scope → FAIL bei Fehlen (wie bisher).
  - Ausgeschlossene Rechtsform (Verein→Handelsregister) → NOT_APPLICABLE.
  - status (Verdikt) ist getrennt von severity (Risiko).
"""

from __future__ import annotations

import asyncio

import pytest

from compliance.api.agent_check._agent_outputs import _derive_scope
from compliance.services.specialist_agents import AgentInput
from compliance.services.specialist_agents._base import CheckStatus, Severity
from compliance.services.specialist_agents.impressum._classification import (
    scan_context_to_scope,
)
from compliance.services.specialist_agents.impressum.agent import ImpressumAgent
from compliance.services.specialist_agents.impressum.mcs import (
    MCS,
    detect_legal_form_present,
    scope_disposition,
)

_MC009 = next(m for m in MCS if m.mc_id == "IMP-MC-009")

# Pflicht-Felder vorhanden (Name/Email/Telefon) ABER keine Rechtsform,
# kein HR, keine Vertretungsangabe im Text.
TEXT_NO_LEGAL_FORM = (
    "Angaben gemäß § 5 TMG\n\n"
    "Muster Consulting\n"
    "Musterstraße 1\n"
    "12345 Berlin\n\n"
    "E-Mail: info@example.com\n"
    "Telefon: +49 30 1234567\n"
    "Weitere Hinweise finden Sie auf unserer Seite.\n"
)
# Gleicher Text, aber Rechtsform GmbH im Text → registerpflichtig erkennbar.
TEXT_WITH_GMBH = TEXT_NO_LEGAL_FORM.replace(
    "Muster Consulting", "Muster Consulting GmbH")


@pytest.fixture(autouse=True)
def _llm_offline(monkeypatch):
    async def _no_validate(*_a, **_kw):
        return {}
    monkeypatch.setattr(
        "compliance.services.specialist_agents.impressum.agent.validate_present",
        _no_validate, raising=False,
    )


def _run(scan_context: dict | None, text: str = TEXT_NO_LEGAL_FORM):
    agent = ImpressumAgent()
    scope = scan_context_to_scope(scan_context) if scan_context else []
    return asyncio.run(agent.evaluate(AgentInput(
        doc_type="impressum", text=text, business_scope=scope)))


def _by_field(out, field_id):
    return next((f for f in out.findings if f.field_id == field_id), None)


# ── detect_legal_form_present ───────────────────────────────────────


def test_detector_recognizes_rechtsform():
    assert detect_legal_form_present("Muster Consulting GmbH")
    assert detect_legal_form_present("Beispiel AG, Berlin")
    assert detect_legal_form_present("Max Mustermann e.K.")


def test_detector_no_rechtsform():
    assert not detect_legal_form_present(TEXT_NO_LEGAL_FORM)
    assert not detect_legal_form_present("Muster Consulting, Berlin")


# ── Unknown ≠ Fail: unbestimmte Rechtsform → INSUFFICIENT_EVIDENCE ──


def test_unknown_legal_form_is_insufficient_not_fail():
    out = _run(None)  # kein Wizard-Scope, keine Rechtsform im Text
    hr = _by_field(out, "handelsregister")
    vt = _by_field(out, "vertretungsberechtigte")
    assert hr is not None and vt is not None
    for f in (hr, vt):
        assert f.status == CheckStatus.INSUFFICIENT_EVIDENCE.value
        assert f.severity == Severity.INFO.value          # kein HIGH!
        assert f.severity_reason == "rechtsform_unbestimmt"


def test_insufficient_evidence_counted_in_aggregate():
    out = _run(None)
    assert out.mc_insufficient >= 2
    cov = {c.mc_id: c.status for c in out.mc_coverage}
    assert cov["IMP-MC-004"] == "insufficient_evidence"
    assert cov["IMP-MC-006"] == "insufficient_evidence"


def test_insufficient_findings_produce_no_recommendation():
    # Hinweise sind keine Pflicht-Massnahmen → kein Rollup.
    out = _run(None)
    insuf_ids = {f.check_id for f in out.findings
                 if f.status == CheckStatus.INSUFFICIENT_EVIDENCE.value}
    for rec in out.recommendations:
        assert not (set(rec.related_finding_ids) & insuf_ids)


# ── Rechtsform bekannt → FAIL (Text oder Wizard) ────────────────────


def test_rechtsform_in_text_yields_fail():
    out = _run(None, text=TEXT_WITH_GMBH)
    hr = _by_field(out, "handelsregister")
    assert hr is not None
    assert hr.status == CheckStatus.FAIL.value
    assert hr.severity == Severity.HIGH.value


def test_rechtsform_in_wizard_scope_yields_fail():
    out = _run({"legal_form": "gmbh"})   # Text ohne Rechtsform, Wizard kennt sie
    hr = _by_field(out, "handelsregister")
    assert hr is not None
    assert hr.status == CheckStatus.FAIL.value
    assert hr.severity == Severity.HIGH.value


# ── Applicability ≠ Compliance: Verein → NOT_APPLICABLE ─────────────


def test_verein_handelsregister_not_applicable():
    out = _run({"legal_form": "verein"})
    assert _by_field(out, "handelsregister") is None     # kein Finding
    cov = {c.mc_id: c.status for c in out.mc_coverage}
    assert cov["IMP-MC-004"] == "na"


def test_default_finding_status_is_fail():
    # Nicht-rechtsform-abhaengige Pflicht (Name) bleibt FAIL bei Fehlen.
    out = _run(None, text="Angaben gemäß § 5 TMG\n" + "x" * 120)
    name = _by_field(out, "name_anbieter")
    assert name is not None
    assert name.status == CheckStatus.FAIL.value


# ── POSSIBLY_APPLICABLE: §18-MStV-Graubereich (editorial) ───────────


def test_scope_disposition_three_way():
    assert scope_disposition(_MC009, {"editorial"}, False) == "applies"
    assert scope_disposition(_MC009, {"editorial_possible"}, False) == "possible"
    assert scope_disposition(_MC009, set(), False) == "na"


def test_editorial_hard_yields_fail():
    # industry=media (Verlag/Presse) → harte §18-Pflicht.
    out = _run({"industry": "media"})
    red = _by_field(out, "verantwortlicher_redaktion")
    assert red is not None
    assert red.status == CheckStatus.FAIL.value
    assert red.severity == Severity.MEDIUM.value


def test_editorial_possible_yields_possibly_applicable():
    # Corporate-Blog (has_editorial_content, kein media) → Graubereich.
    out = ImpressumAgent()
    out = asyncio.run(out.evaluate(AgentInput(
        doc_type="impressum", text=TEXT_NO_LEGAL_FORM,
        business_scope=["editorial_possible"])))
    red = _by_field(out, "verantwortlicher_redaktion")
    assert red is not None
    assert red.status == CheckStatus.POSSIBLY_APPLICABLE.value
    assert red.severity == Severity.LOW.value
    assert out.mc_possibly >= 1
    assert "§ 18 MStV" in red.action       # MC-spezifischer Hinweis


def test_editorial_absent_is_not_applicable():
    out = _run(None)  # kein editorial-Signal
    assert _by_field(out, "verantwortlicher_redaktion") is None
    cov = {c.mc_id: c.status for c in out.mc_coverage}
    assert cov["IMP-MC-009"] == "na"


def test_derive_scope_editorial_tiers():
    assert "editorial_possible" in _derive_scope({"has_editorial_content": True})
    assert "editorial" in _derive_scope({"industry": "media"})
    # Medienunternehmen gewinnt — nicht beide Tokens.
    s = _derive_scope({"industry": "media", "has_editorial_content": True})
    assert "editorial" in s and "editorial_possible" not in s


# ── §36 VSBG Graubereich (BMW-Fall): reines b2c ≠ harter Verstoß ────


def test_vsbg_b2c_is_possibly_applicable():
    # Reine B2C-Orientierung (z.B. OEM-Markenseite, Verkauf über Händler) →
    # §36 VSBG = Graubereich, KEIN MEDIUM-FAIL (BMW-False-Positive-Fix).
    out = asyncio.run(ImpressumAgent().evaluate(AgentInput(
        doc_type="impressum", text=TEXT_NO_LEGAL_FORM,
        business_scope=["b2c"])))
    vsbg = _by_field(out, "verbraucher_streitbeilegung")
    assert vsbg is not None
    assert vsbg.status == CheckStatus.POSSIBLY_APPLICABLE.value
    assert vsbg.severity == Severity.LOW.value
    assert "VSBG" in vsbg.action          # §36-Hinweis, nicht §18


def test_vsbg_ecommerce_is_hard_fail():
    # Echter Online-Shop (ecommerce) → §36 VSBG harte Pflicht (MEDIUM).
    out = asyncio.run(ImpressumAgent().evaluate(AgentInput(
        doc_type="impressum", text=TEXT_NO_LEGAL_FORM,
        business_scope=["ecommerce"])))
    vsbg = _by_field(out, "verbraucher_streitbeilegung")
    assert vsbg is not None
    assert vsbg.status == CheckStatus.FAIL.value
    assert vsbg.severity == Severity.MEDIUM.value
