"""Tests for the Regulatory Map renderer (step 4).

Acceptance: the renderer makes no own legal decisions (it composes the scope +
registry-linked obligations); CRA/MaschVO/EMV are separate; RED/DataAct/NIS2 are
uncertain; environmental is unsupported (not applicable); obligations appear only
when registry-linkable; the executive summary has no percentage.
"""

from __future__ import annotations

import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient

from compliance.product_scope import resolve_product_scope
from compliance.profile.canonical import (
    CanonicalLifecyclePhase,
    CanonicalProductRegulatoryProfile,
    CanonicalProductType,
    EconomicOperatorRole,
    EnvironmentalImpact,
)
from compliance.regulatory_map import render_regulatory_map

_PROPOSED_IDS = {
    "machine_risk_assessment", "machine_safety_control_systems", "machine_protection_against_corruption",
    "machine_instructions_for_use", "machine_ce_conformity", "data_act_data_access_by_design",
    "data_act_user_data_access", "cra_secure_by_design", "cra_risk_assessment",
    "cra_technical_documentation", "cra_ce_conformity_assessment", "cra_instructions_for_use",
}


def ready_profile(**ov) -> CanonicalProductRegulatoryProfile:
    base = dict(
        name="Industriespülmaschine",
        product_type=CanonicalProductType.MACHINERY,
        markets=["EU", "DE"],
        economic_operator_role=EconomicOperatorRole.MANUFACTURER,
        lifecycle_phase=CanonicalLifecyclePhase.PLACING_ON_MARKET,
        is_machine=True,
        is_component=False,
        has_software_updates=True,
        has_embedded_software=True,
        has_remote_access=True,
        technologies=["cloud", "ota_updates"],
    )
    base.update(ov)
    return CanonicalProductRegulatoryProfile(**base)


# 1. renderer makes no own decisions — it mirrors the scope verdict exactly.
def test_no_own_legal_decisions():
    p = ready_profile()
    m = render_regulatory_map(p)
    scope = resolve_product_scope(p).regulatory_scope
    assert {v.regulation_id for v in m.applicable_regulations} == {
        r.regulation_id for r in scope.applicable_regulations
    }
    assert {v.regulation_id for v in m.uncertain_regulations} == {
        r.regulation_id for r in scope.uncertain_regulations
    }


# 2/3/5. CRA/MaschVO/EMV separate applicable; RED/DataAct/NIS2 uncertain.
def test_regulation_separation():
    m = render_regulatory_map(ready_profile())
    applicable = {v.regulation_id for v in m.applicable_regulations}
    uncertain = {v.regulation_id for v in m.uncertain_regulations}
    assert {"CRA", "MaschinenVO", "EMV"} <= applicable
    assert {"RED", "DataAct", "NIS2"} <= uncertain


# 4. environmental triggers surface as unsupported_domain, never applicable.
def test_environmental_unsupported_not_applicable():
    p = ready_profile(environmental=EnvironmentalImpact(discharges_to_wastewater=True, uses_cleaning_chemicals=True))
    m = render_regulatory_map(p)
    domains = {d.domain for d in m.unsupported_domains}
    assert "environment_water" in domains and "chemicals" in domains
    assert all(v.regulation_id in {"CRA", "MaschinenVO", "RED", "DataAct", "EMV", "NIS2"} for v in m.applicable_regulations)


# 6. obligations are shown only when a registry id is linkable.
def test_obligations_only_registry_linkable():
    m = render_regulatory_map(ready_profile())
    shown = {o.obligation_id for v in m.applicable_regulations for o in v.obligations}
    assert shown  # CRA registry obligations are shown
    assert "sbom_creation" in shown
    assert not (shown & _PROPOSED_IDS)  # no proposed (non-registry) obligation leaks in
    # MaschinenVO is applicable but its obligations are proposed -> empty + note
    machvo = next(v for v in m.applicable_regulations if v.regulation_id == "MaschinenVO")
    assert machvo.obligations == []
    assert machvo.obligations_note


# 7. executive summary contains no percentage.
def test_executive_summary_no_percent():
    m = render_regulatory_map(ready_profile())
    assert "%" not in m.executive_summary
    assert "prozent" not in m.executive_summary.lower()


# 8. output is customer-readable and structured.
def test_customer_readable():
    m = render_regulatory_map(ready_profile())
    assert m.product_summary
    assert "wahrscheinlich" in m.executive_summary
    assert "Unsicher" in m.executive_summary
    assert m.trigger_facts


# needs-facts profile -> map says scope not yet resolved.
def test_needs_facts_map():
    m = render_regulatory_map(CanonicalProductRegulatoryProfile(name="X"))
    assert m.scope_resolved is False
    assert "Mindestfakten" in m.executive_summary
    assert m.applicable_regulations == []


# uncertain RED links to the radio navigator question.
def test_uncertain_links_to_navigator_question():
    m = render_regulatory_map(ready_profile())
    red = next(v for v in m.uncertain_regulations if v.regulation_id == "RED")
    assert "has_radio_module" in red.question_refs


# endpoint smoke.
@pytest.fixture(scope="module")
def client():
    from compliance.api.reasoning_routes import router

    app = FastAPI()
    app.include_router(router)
    return TestClient(app)


def test_endpoint_regulatory_map(client):
    r = client.post(
        "/reasoning/regulatory-map",
        json={
            "product_profile": {
                "name": "M",
                "product_type": "machinery",
                "markets": ["EU"],
                "economic_operator_role": "manufacturer",
                "lifecycle_phase": "placing_on_market",
                "is_machine": True,
                "is_component": False,
                "has_software_updates": True,
                "has_embedded_software": True,
                "has_remote_access": True,
                "technologies": ["cloud"],
            }
        },
    )
    assert r.status_code == 200
    body = r.json()
    assert body["scope_resolved"] is True
    assert {v["regulation_id"] for v in body["applicable_regulations"]} >= {"CRA", "MaschinenVO"}
    assert "%" not in body["executive_summary"]
