"""Tests for the product-scope orchestrator (step 3).

Acceptance: missing P0 facts -> discover_scope NOT run; ready -> run exactly once;
response separates applicable/excluded/uncertain; environmental triggers appear
only as unsupported_domain (future_corpus_needed), never as a legal evaluation.
"""

from __future__ import annotations

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

import compliance.product_scope.orchestrator as orch
from compliance.product_scope import ScopeStatus, resolve_product_scope
from compliance.profile.canonical import (
    CanonicalLifecyclePhase,
    CanonicalProductRegulatoryProfile,
    CanonicalProductType,
    EconomicOperatorRole,
    EnvironmentalImpact,
)

_KNOWN_REGS = {"CRA", "MaschinenVO", "RED", "EMV", "DataAct", "NIS2"}


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,
        has_safety_function=True,
        technologies=["cloud", "ota_updates"],
    )
    base.update(ov)
    return CanonicalProductRegulatoryProfile(**base)


def _spy(monkeypatch):
    calls = {"n": 0}
    real = orch.discover_scope

    def counting(profile):
        calls["n"] += 1
        return real(profile)

    monkeypatch.setattr(orch, "discover_scope", counting)
    return calls


# 1. missing P0 facts -> discover_scope is NOT executed.
def test_needs_facts_does_not_run_scope(monkeypatch):
    calls = _spy(monkeypatch)
    resp = resolve_product_scope(CanonicalProductRegulatoryProfile(name="X"))
    assert resp.status == ScopeStatus.NEEDS_FACTS
    assert resp.regulatory_scope is None
    assert resp.missing_facts
    assert calls["n"] == 0


# 2. ready_for_scope -> discover_scope runs exactly once.
def test_ready_runs_scope_once(monkeypatch):
    calls = _spy(monkeypatch)
    resp = resolve_product_scope(ready_profile())
    assert resp.status == ScopeStatus.RESOLVED
    assert resp.regulatory_scope is not None
    assert calls["n"] == 1
    applicable = {r.regulation_id for r in resp.regulatory_scope.applicable_regulations}
    assert "CRA" in applicable and "MaschinenVO" in applicable


# 3. the response separates the regulation categories.
def test_response_separates_categories():
    scope = resolve_product_scope(ready_profile()).regulatory_scope
    assert scope is not None
    # all three buckets exist and only carry known regulation ids
    for bucket in (scope.applicable_regulations, scope.excluded_regulations, scope.uncertain_regulations):
        for r in bucket:
            assert r.regulation_id in _KNOWN_REGS
    assert scope.uncertain_regulations  # e.g. RED/DataAct/NIS2 with unknown facts


# 4. environmental triggers surface ONLY as unsupported_domain, never as law.
def test_environmental_only_unsupported_domain():
    profile = ready_profile(
        environmental=EnvironmentalImpact(discharges_to_wastewater=True, uses_cleaning_chemicals=True)
    )
    scope = resolve_product_scope(profile).regulatory_scope
    assert scope is not None
    domains = {d.domain for d in scope.unsupported_domains}
    assert "environment_water" in domains and "chemicals" in domains
    assert all(d.status == "future_corpus_needed" for d in scope.unsupported_domains)
    # no environmental "regulation" leaked into the scope verdict
    all_regs = (
        scope.applicable_regulations + scope.excluded_regulations + scope.uncertain_regulations
    )
    assert all(r.regulation_id in _KNOWN_REGS for r in all_regs)


# 5. endpoint smoke — both cases.
@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_needs_facts(client):
    r = client.post("/reasoning/product-scope", json={"product_profile": {"name": "X"}})
    assert r.status_code == 200
    body = r.json()
    assert body["status"] == "needs_facts"
    assert body["regulatory_scope"] is None
    assert body["missing_facts"]


def test_endpoint_resolved(client):
    r = client.post(
        "/reasoning/product-scope",
        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["status"] == "resolved"
    applicable = {x["regulation_id"] for x in body["regulatory_scope"]["applicable_regulations"]}
    assert "CRA" in applicable and "MaschinenVO" in applicable
