"""Tests for the Regulatory Optimization renderer (Roadmap / Management view of the Capability Delta).

Acceptance: rank measures by regulatory leverage (most regulatory requirements closed at once),
report the compression (identified requirements -> measures), answer the budget question, and bind
to the SAME RS-005 Capability Delta the Interview Renderer uses. Percentages are over IDENTIFIED
requirements (Welt-1), never "% gesetzeskonform".
"""

from __future__ import annotations

from compliance.optimization import (
    BudgetPlan, OptimizationPlan, regulatory_leverage, roadmap_from_delta, select_within_budget,
)
from compliance.transition_reasoning import (
    CapabilityCoverage, CoverageStatus, TransitionAssessment, TransitionSummary,
)

# Illustrative leverage spread (the user's patch_management example reaches leverage 4).
CAPS = {
    "patch_management": ["CRA", "MaschinenVO", "IEC62443", "ISO27001"],  # leverage 4
    "access_control": ["CRA", "ISO27001"],                              # leverage 2
    "sbom": ["CRA"],                                                    # leverage 1
    "machine_guards": ["MaschinenVO"],                                  # leverage 1
}


def test_ranked_by_leverage_desc():
    plan = regulatory_leverage(CAPS)
    order = [m.capability_id for m in plan.ranked_measures]
    assert order[0] == "patch_management"           # leverage 4 first
    assert order[1] == "access_control"             # leverage 2 next
    assert plan.ranked_measures[0].leverage == 4


def test_compression_counts():
    plan = regulatory_leverage(CAPS)
    # total requirements = 4 + 2 + 1 + 1 = 8 closed by 4 measures
    assert plan.total_requirements == 8 and plan.total_measures == 4
    assert "8 identifizierte Anforderungen" in plan.headline


def test_cumulative_coverage_monotone_to_one():
    plan = regulatory_leverage(CAPS)
    cums = [m.cumulative_coverage for m in plan.ranked_measures]
    assert cums == sorted(cums)                     # non-decreasing
    assert abs(cums[-1] - 1.0) < 1e-9               # full set -> 100%
    assert plan.ranked_measures[0].cumulative_requirements == 4


def test_tie_break_deterministic_by_id():
    # machine_guards vs sbom both leverage 1 -> alphabetical: machine_guards before sbom
    plan = regulatory_leverage(CAPS)
    tail = [m.capability_id for m in plan.ranked_measures if m.leverage == 1]
    assert tail == ["machine_guards", "sbom"]


def test_budget_picks_highest_leverage():
    b = select_within_budget(CAPS, 2)
    assert b.selected_capabilities == ["patch_management", "access_control"]
    assert b.requirements_closed == 6 and b.total_requirements == 8
    assert abs(b.coverage_ratio - 0.75) < 1e-9
    assert "6 von 8" in b.headline and "75%" in b.headline


def test_budget_over_and_zero():
    assert select_within_budget(CAPS, 99).requirements_closed == 8     # capped at all
    z = select_within_budget(CAPS, 0)
    assert z.selected_capabilities == [] and z.requirements_closed == 0


def test_in_scope_filter():
    # restrict to CRA + MaschinenVO: patch=2, access=1(CRA), sbom=1, guards=1 -> total 5
    plan = regulatory_leverage(CAPS, in_scope=["CRA", "MaschinenVO"])
    assert plan.total_requirements == 5
    assert plan.ranked_measures[0].capability_id == "patch_management"
    assert plan.ranked_measures[0].leverage == 2                       # only CRA+MaschinenVO counted


def test_deterministic():
    a, b = regulatory_leverage(CAPS), regulatory_leverage(CAPS)
    assert [m.capability_id for m in a.ranked_measures] == [m.capability_id for m in b.ranked_measures]
    assert a.headline == b.headline


def test_empty():
    plan = regulatory_leverage({})
    assert plan.total_requirements == 0 and plan.ranked_measures == []
    assert isinstance(plan, OptimizationPlan)


def test_capability_with_no_in_scope_requirement_dropped():
    plan = regulatory_leverage({"x": ["CRA"], "y": ["DataAct"]}, in_scope=["CRA"])
    assert [m.capability_id for m in plan.ranked_measures] == ["x"]    # y covers nothing in scope


# The keystone: optimization renders the SAME RS-005 delta the interview uses.
def _delta():
    return TransitionAssessment(
        target_id="CRA+MaschinenVO",
        coverage=[
            CapabilityCoverage(capability_id="patch_management", status=CoverageStatus.MISSING),
            CapabilityCoverage(capability_id="sbom", status=CoverageStatus.MISSING),
            CapabilityCoverage(capability_id="access_control", status=CoverageStatus.ALREADY_COVERED),
            CapabilityCoverage(capability_id="machine_guards", status=CoverageStatus.MISSING),
        ],
        summary=TransitionSummary(),
    )


def test_roadmap_from_delta_uses_only_open_capabilities():
    plan = roadmap_from_delta(_delta(), CAPS)
    ids = [m.capability_id for m in plan.ranked_measures]
    assert "access_control" not in ids              # ALREADY_COVERED is not an open measure
    assert ids == ["patch_management", "machine_guards", "sbom"]  # MISSING ranked by leverage
    assert isinstance(plan, OptimizationPlan)


def test_roadmap_from_delta_honours_status_filter():
    # include NEEDS_CONFIRMATION too -> still only those present in CAPS contribute requirements
    a = _delta()
    a.coverage.append(CapabilityCoverage(capability_id="access_control", status=CoverageStatus.NEEDS_CONFIRMATION))
    plan = roadmap_from_delta(a, CAPS, open_statuses=[CoverageStatus.MISSING, CoverageStatus.NEEDS_CONFIRMATION])
    assert "access_control" in [m.capability_id for m in plan.ranked_measures]


def test_budget_returns_budgetplan_type():
    assert isinstance(select_within_budget(CAPS, 1), BudgetPlan)
