"""CRA Art. 14 reporting cascade — pure deadline + ENISA-export logic."""
import pytest

from compliance.services.cra_meldewesen import (
    compute_deadlines, next_open_stage, build_enisa_report, report_completeness,
)

AWARE = "2026-09-15T08:00:00+00:00"


class TestDeadlines:
    def test_due_times_are_24h_72h_14d_after_awareness(self):
        d = compute_deadlines(AWARE, now_iso=AWARE)
        by = {x["key"]: x for x in d}
        assert by["early_warning"]["due_at"] == "2026-09-16T08:00:00+00:00"
        assert by["notification"]["due_at"] == "2026-09-18T08:00:00+00:00"
        assert by["final"]["due_at"] == "2026-09-29T08:00:00+00:00"

    def test_all_pending_right_after_awareness(self):
        d = compute_deadlines(AWARE, now_iso="2026-09-15T09:00:00+00:00")
        assert all(x["status"] == "pending" for x in d)

    def test_overdue_when_now_past_due_and_unsubmitted(self):
        d = compute_deadlines(AWARE, now_iso="2026-09-17T00:00:00+00:00")
        by = {x["key"]: x for x in d}
        assert by["early_warning"]["status"] == "overdue"   # 24h passed
        assert by["notification"]["status"] == "pending"    # 72h not yet

    def test_due_soon_within_window(self):
        # 4h before the 24h deadline → due_soon
        d = compute_deadlines(AWARE, now_iso="2026-09-16T04:00:00+00:00")
        by = {x["key"]: x for x in d}
        assert by["early_warning"]["status"] == "due_soon"

    def test_submitted_overrides_timing(self):
        d = compute_deadlines(
            AWARE, submissions={"early_warning": "2026-09-16T07:00:00+00:00"},
            now_iso="2026-09-20T00:00:00+00:00")
        by = {x["key"]: x for x in d}
        assert by["early_warning"]["status"] == "submitted"
        assert by["notification"]["status"] == "overdue"

    def test_next_open_stage(self):
        d = compute_deadlines(AWARE, submissions={"early_warning": AWARE}, now_iso=AWARE)
        assert next_open_stage(d) == "notification"
        d2 = compute_deadlines(
            AWARE, submissions={k: AWARE for k in ("early_warning", "notification", "final")},
            now_iso=AWARE)
        assert next_open_stage(d2) is None


class TestEnisaReport:
    def _incident(self):
        return {
            "manufacturer": "OWIS GmbH", "product_name": "PS 90+", "product_version": "2.1",
            "kind": "exploited_vulnerability", "severity": "high", "aware_at": AWARE,
            "summary": "Aktiv ausgenutzte Auth-Umgehung", "contact": "psirt@owis.eu",
            "impact": "Fernsteuerung möglich", "root_cause": "fehlende Tokenprüfung",
            "patch_available": True,
        }

    def test_early_warning_has_base_not_detail(self):
        r = build_enisa_report(self._incident(), "early_warning")
        assert r["manufacturer"] == "OWIS GmbH" and r["severity"] == "high"
        assert "impact" not in r and "root_cause" not in r
        assert r["submission_target"].startswith("ENISA")

    def test_notification_adds_impact_not_rootcause(self):
        r = build_enisa_report(self._incident(), "notification")
        assert r["impact"] == "Fernsteuerung möglich"
        assert "root_cause" not in r

    def test_final_adds_root_cause_and_patch(self):
        r = build_enisa_report(self._incident(), "final")
        assert r["root_cause"] == "fehlende Tokenprüfung"
        assert r["patch_available"] is True

    def test_unknown_stage_raises(self):
        with pytest.raises(ValueError):
            build_enisa_report(self._incident(), "nonsense")

    def test_completeness_flags_missing(self):
        thin = {"manufacturer": "X", "aware_at": AWARE}
        c = report_completeness(thin, "early_warning")
        assert not c["complete"]
        assert "product_name" in c["missing"]
        assert "manufacturer" in c["filled"]
