from __future__ import annotations

import pytest

from grantflow.swarm.critic_donor_policy import apply_donor_specific_toc_checks, normalized_donor_id


def _run_policy(donor_id: str, toc_payload):
    checks: list[dict] = []
    flaws: list[dict] = []

    def check_fn(**kwargs):
        checks.append(dict(kwargs))

    def add_flaw_fn(**kwargs):
        flaws.append(dict(kwargs))

    apply_donor_specific_toc_checks(
        state={"donor_id": donor_id},
        toc_payload=toc_payload,
        check_fn=check_fn,
        add_flaw_fn=add_flaw_fn,
    )
    return checks, flaws


def test_normalized_donor_id_prefers_donor_id_and_lowercases():
    assert normalized_donor_id({"donor_id": " USAID "}) == "usaid"
    assert normalized_donor_id({"donor": " GIZ "}) == "giz"


def test_unknown_donor_produces_no_checks_or_flaws():
    checks, flaws = _run_policy("unknown_donor", {"project_goal": "Example"})
    assert checks == []
    assert flaws == []


@pytest.mark.parametrize(
    ("donor_id", "toc_payload", "expected_check_code", "expected_check_status", "expected_flaw_code"),
    [
        ("usaid", {"project_goal": "Goal"}, "USAID_DO_PRESENT", "fail", "USAID_DO_MISSING"),
        ("eu", {}, "EU_OVERALL_OBJECTIVE_COMPLETE", "fail", "EU_OVERALL_OBJECTIVE_MISSING"),
        ("giz", {"outcomes": []}, "GIZ_OUTCOMES_PRESENT", "fail", "GIZ_OUTCOMES_MISSING"),
        (
            "state_department",
            {"stakeholder_map": [], "risk_mitigation": []},
            "STATE_STRATEGIC_CONTEXT_PRESENT",
            "fail",
            "STATE_STRATEGIC_CONTEXT_PRESENT_MISSING",
        ),
        ("worldbank", {"objectives": []}, "WB_OBJECTIVES_PRESENT", "fail", "WB_OBJECTIVES_MISSING"),
    ],
)
def test_donor_policy_emits_expected_failure_signals(
    donor_id, toc_payload, expected_check_code, expected_check_status, expected_flaw_code
):
    checks, flaws = _run_policy(donor_id, toc_payload)
    assert any(c["code"] == expected_check_code and c["status"] == expected_check_status for c in checks)
    assert any(f["code"] == expected_flaw_code for f in flaws)


def test_state_department_alias_uses_same_policy_rules():
    checks, flaws = _run_policy("us_state_department", {})
    assert any(c["code"] == "STATE_STRATEGIC_CONTEXT_PRESENT" for c in checks)
    assert any(f["code"] == "STATE_STRATEGIC_CONTEXT_PRESENT_MISSING" for f in flaws)


def test_usaid_complete_hierarchy_passes_core_checks_without_high_flaws():
    toc_payload = {
        "project_goal": "Improve services",
        "development_objectives": [
            {
                "do_id": "DO 1",
                "description": "DO",
                "intermediate_results": [
                    {
                        "ir_id": "IR 1.1",
                        "description": "IR",
                        "outputs": [{"output_id": "Output 1.1.1", "description": "Output", "indicators": []}],
                    }
                ],
            }
        ],
        "critical_assumptions": ["Assumption 1"],
    }
    checks, flaws = _run_policy("usaid", toc_payload)

    assert any(c["code"] == "USAID_DO_PRESENT" and c["status"] == "pass" for c in checks)
    assert any(c["code"] == "USAID_IR_HIERARCHY" and c["status"] == "pass" for c in checks)
    assert any(c["code"] == "USAID_OUTPUT_HIERARCHY" and c["status"] == "pass" for c in checks)
    assert any(c["code"] == "USAID_CRITICAL_ASSUMPTIONS_PRESENT" and c["status"] == "pass" for c in checks)
    assert not any(f["severity"] == "high" for f in flaws)


def test_eu_complete_payload_passes_new_structure_checks():
    toc_payload = {
        "overall_objective": {
            "objective_id": "OO1",
            "title": "Inclusive growth",
            "rationale": "Aligned with EU country priorities",
        },
        "specific_objectives": [
            {
                "objective_id": "SO1",
                "title": "Improve SME access",
                "rationale": "Supports jobs and resilience",
            }
        ],
        "expected_outcomes": [
            {
                "outcome_id": "OUT1",
                "title": "More SMEs financed",
                "expected_change": "15% increase in formal credit uptake",
            }
        ],
        "safeguarding_annex": [
            "Safeguarding protocol with do-no-harm and survivor-centered safeguards",
            "Referral and escalation pathway with handoff steps",
            "Risk owner matrix with named focal points",
            "Compliance checkpoint and evidence verification before submission",
        ],
    }
    checks, flaws = _run_policy("eu", toc_payload)
    assert any(c["code"] == "EU_OVERALL_OBJECTIVE_COMPLETE" and c["status"] == "pass" for c in checks)
    assert any(c["code"] == "EU_SPECIFIC_OBJECTIVES_COMPLETE" and c["status"] == "pass" for c in checks)
    assert any(c["code"] == "EU_EXPECTED_OUTCOMES_PRESENT" and c["status"] == "pass" for c in checks)
    assert any(c["code"] == "EU_SAFEGUARDING_ANNEX_PRESENT" and c["status"] == "pass" for c in checks)
    assert any(c["code"] == "EU_SAFEGUARDING_ANNEX_MINIMUM_BLOCKS" and c["status"] == "pass" for c in checks)
    assert not any(f["severity"] == "high" for f in flaws)


def test_worldbank_complete_payload_passes_structure_checks():
    toc_payload = {
        "project_development_objective": "Improve service delivery outcomes in target districts",
        "objectives": [
            {
                "objective_id": "OBJ1",
                "title": "Strengthen frontline systems",
                "description": "Improve local management and accountability",
            }
        ],
        "results_chain": [
            {
                "result_id": "R1",
                "title": "Improved planning quality",
                "description": "District plans include measurable service targets",
                "indicator_focus": "District plans with measurable targets (%)",
            }
        ],
    }
    checks, flaws = _run_policy("worldbank", toc_payload)
    assert any(c["code"] == "WB_PDO_PRESENT" and c["status"] == "pass" for c in checks)
    assert any(c["code"] == "WB_OBJECTIVES_PRESENT" and c["status"] == "pass" for c in checks)
    assert any(c["code"] == "WB_OBJECTIVES_COMPLETE" and c["status"] == "pass" for c in checks)
    assert any(c["code"] == "WB_RESULTS_CHAIN_PRESENT" and c["status"] == "pass" for c in checks)
    assert any(c["code"] == "WB_RESULTS_CHAIN_COMPLETE" and c["status"] == "pass" for c in checks)
    assert not any(f["severity"] == "high" for f in flaws)


def test_usaid_missing_do_uses_reviewer_oriented_language():
    _checks, flaws = _run_policy("usaid", {"project_goal": "Goal"})
    flaw = next(f for f in flaws if f["code"] == "USAID_DO_MISSING")
    assert "donor package" in flaw["message"].lower()
    assert "reviewer expectations" in flaw["fix_hint"].lower()


def test_eu_missing_overall_objective_uses_intervention_logic_language():
    _checks, flaws = _run_policy("eu", {})
    flaw = next(f for f in flaws if f["code"] == "EU_OVERALL_OBJECTIVE_MISSING")
    assert "credible top-line anchor" in flaw["message"].lower()
    assert "verification logic" in flaw["fix_hint"].lower()


def test_eu_missing_safeguarding_annex_emits_readiness_flaw():
    _checks, flaws = _run_policy(
        "eu",
        {
            "overall_objective": {"objective_id": "OO1", "title": "T", "rationale": "R"},
            "specific_objectives": [{"objective_id": "SO1", "title": "T", "rationale": "R"}],
            "expected_outcomes": [{"outcome_id": "OUT1", "title": "T", "expected_change": "C"}],
        },
    )
    flaw = next(f for f in flaws if f["code"] == "EU_SAFEGUARDING_ANNEX_MISSING")
    assert "safeguarding annex" in flaw["message"].lower()


def test_worldbank_missing_results_chain_uses_framework_review_language():
    _checks, flaws = _run_policy("worldbank", {"objectives": []})
    flaw = next(f for f in flaws if f["code"] == "WB_RESULTS_CHAIN_MISSING")
    assert "trace the pdo" in flaw["fix_hint"].lower()
    assert "isr-style monitoring" in flaw["fix_hint"].lower()


def test_giz_missing_partner_role_uses_operational_review_language():
    _checks, flaws = _run_policy(
        "giz",
        {
            "outcomes": [{"title": "Outcome 1", "partner_role": ""}],
            "sustainability_factors": ["Institutional uptake"],
        },
    )
    flaw = next(f for f in flaws if f["code"] == "GIZ_PARTNER_ROLE_MISSING")
    assert "operationally review-ready" in flaw["message"].lower()
    assert "delivery ownership" in flaw["fix_hint"].lower()


def test_state_department_missing_context_uses_triage_anchor_language():
    _checks, flaws = _run_policy("state_department", {"stakeholder_map": [], "risk_mitigation": []})
    flaw = next(f for f in flaws if f["code"] == "STATE_STRATEGIC_CONTEXT_PRESENT_MISSING")
    assert "core reviewer triage anchor" in flaw["message"].lower()
