from __future__ import annotations

from grantflow.swarm.critic_rules import evaluate_rule_based_critic
from grantflow.swarm.nodes.critic import RedTeamEvaluation, _llm_flaws_to_structured, red_team_critic


def test_rule_based_critic_emits_structured_flaws_with_section_and_version():
    state = {
        "draft_versions": [
            {"version_id": "toc_v1", "sequence": 1, "section": "toc", "content": {}},
            {"version_id": "logframe_v1", "sequence": 2, "section": "logframe", "content": {}},
        ],
        "toc_validation": {"valid": False, "errors": ["missing field"], "schema_name": "GenericTOC"},
        "toc_draft": {"toc": {"project_goal": "Goal text"}},
        "logframe_draft": {"indicators": []},
        "citations": [],
    }
    report = evaluate_rule_based_critic(state)

    assert report.fatal_flaws
    flaws = [f.model_dump() if hasattr(f, "model_dump") else f.dict() for f in report.fatal_flaws]
    assert any(
        f["code"] == "TOC_SCHEMA_INVALID" and f["section"] == "toc" and f["version_id"] == "toc_v1" for f in flaws
    )
    assert any(
        f["code"] == "LOGFRAME_INDICATORS_MISSING" and f["section"] == "logframe" and f["version_id"] == "logframe_v1"
        for f in flaws
    )
    assert report.score < 8.0
    assert report.checks


def test_red_team_evaluation_coerces_string_fatal_flaws_to_entities():
    payload = RedTeamEvaluation.model_validate(
        {
            "score": 7.0,
            "fatal_flaws": [
                "Objective is too broad.",
                {"message": "Indicator baseline missing.", "section": "logframe"},
            ],
            "revision_instructions": "Tighten objectives and indicators.",
        }
    )
    assert isinstance(payload.fatal_flaws, list)
    assert len(payload.fatal_flaws) == 2
    assert payload.fatal_flaws[0]["message"] == "Objective is too broad."
    assert payload.fatal_flaws[1]["message"] == "Indicator baseline missing."


def test_red_team_critic_uses_rules_without_llm_and_stores_structured_notes():
    state = {
        "donor_strategy": object(),
        "strategy": object(),
        "llm_mode": False,
        "max_iterations": 3,
        "iteration": 0,
        "iteration_count": 0,
        "toc_draft": {
            "toc": {"project_goal": "Improve access", "objectives": [{"title": "Obj", "description": "Desc"}]}
        },
        "logframe_draft": {"indicators": [{"indicator_id": "IND_001"}]},
        "citations": [
            {"stage": "architect", "statement_path": "toc.project_goal", "label": "doc", "used_for": "toc_claim"},
            {"stage": "mel", "label": "doc", "used_for": "IND_001"},
        ],
        "draft_versions": [
            {"version_id": "toc_v1", "sequence": 1, "section": "toc", "content": {}},
            {"version_id": "logframe_v1", "sequence": 2, "section": "logframe", "content": {}},
        ],
        "toc_validation": {"valid": True, "errors": [], "schema_name": "GenericTOC"},
        "critic_feedback_history": [],
    }

    out = red_team_critic(state)
    notes = out.get("critic_notes") or {}
    assert notes.get("engine") == "rules"
    assert isinstance(notes.get("fatal_flaws"), list)
    if notes.get("fatal_flaws"):
        flaw = notes["fatal_flaws"][0]
        assert flaw.get("finding_id")
        assert flaw.get("status") in {"open", "acknowledged", "resolved"}
    assert isinstance(notes.get("rule_checks"), list)
    assert "revision_instructions" in notes
    assert out.get("next_step") in {"architect", "end"}


def test_red_team_critic_syncs_findings_aliases_as_entities():
    state = {
        "donor_strategy": object(),
        "strategy": object(),
        "llm_mode": False,
        "max_iterations": 3,
        "iteration_count": 0,
        "toc_draft": {"toc": {"project_goal": "Improve access"}},
        "logframe_draft": {"indicators": []},
        "citations": [],
        "draft_versions": [{"version_id": "toc_v1", "sequence": 1, "section": "toc", "content": {}}],
        "toc_validation": {"valid": False, "errors": ["missing field"], "schema_name": "GenericTOC"},
        "critic_feedback_history": [],
    }
    out = red_team_critic(state)
    notes = out.get("critic_notes")
    assert isinstance(notes, dict)
    notes_flaws = notes.get("fatal_flaws")
    assert isinstance(notes_flaws, list)
    alias_flaws = out.get("critic_fatal_flaws")
    assert isinstance(alias_flaws, list)
    assert notes_flaws == alias_flaws
    assert notes_flaws
    for flaw in notes_flaws:
        assert isinstance(flaw, dict)
        assert flaw.get("finding_id")
        assert flaw.get("id") == flaw.get("finding_id")
        assert flaw.get("status") in {"open", "acknowledged", "resolved"}
        assert flaw.get("code")
        assert flaw.get("section") in {"toc", "logframe", "general"}
        assert flaw.get("message")


def test_red_team_critic_marks_sparse_input_brief_for_revision():
    state = {
        "donor_strategy": object(),
        "strategy": object(),
        "llm_mode": False,
        "max_iterations": 3,
        "iteration": 0,
        "iteration_count": 0,
        "input_context": {"project": "AI governance training"},
        "toc_draft": {
            "toc": {"project_goal": "Improve access", "objectives": [{"title": "Obj", "description": "Desc"}]}
        },
        "logframe_draft": {"indicators": [{"indicator_id": "IND_001"}]},
        "citations": [
            {"stage": "architect", "statement_path": "toc.project_goal", "label": "doc", "used_for": "toc_claim"},
            {"stage": "mel", "label": "doc", "used_for": "IND_001"},
        ],
        "draft_versions": [
            {"version_id": "toc_v1", "sequence": 1, "section": "toc", "content": {}},
            {"version_id": "logframe_v1", "sequence": 2, "section": "logframe", "content": {}},
        ],
        "toc_validation": {"valid": True, "errors": [], "schema_name": "GenericTOC"},
        "critic_feedback_history": [],
    }

    out = red_team_critic(state)
    notes = out.get("critic_notes") or {}
    flaws = notes.get("fatal_flaws") or []
    assert any((f or {}).get("code") == "INPUT_BRIEF_TOO_SPARSE" for f in flaws)
    assert out.get("needs_revision") is True
    assert out.get("next_step") == "architect"


def test_llm_flaw_normalization_binds_to_latest_section_versions():
    state = {
        "draft_versions": [
            {"version_id": "toc_v1", "sequence": 1, "section": "toc", "content": {}},
            {"version_id": "toc_v2", "sequence": 2, "section": "toc", "content": {}},
            {"version_id": "logframe_v1", "sequence": 3, "section": "logframe", "content": {}},
        ]
    }
    llm_flaws = [
        {"code": "L1", "section": "toc", "message": "Objective wording is too broad."},
        {"code": "L2", "section": "logframe", "message": "Indicator target is missing."},
        {"code": "L3", "section": "general", "message": "Clarify delivery sequencing and scope boundaries."},
    ]

    rows = _llm_flaws_to_structured(llm_flaws, state=state)
    by_code = {row["code"]: row for row in rows}
    assert by_code["L1"]["version_id"] == "toc_v2"
    assert by_code["L2"]["version_id"] == "logframe_v1"
    assert by_code["L3"]["version_id"] is None
