# grantflow/tests/test_integration_critic_single_status.py

from fastapi.testclient import TestClient

from grantflow.api import runtime_state
from grantflow.api.app import app

client = TestClient(app)


def test_status_critic_single_status_dry_run_previews_without_persisting():
    job_id = "critic-findings-single-dry-run-1"
    runtime_state.JOB_STORE.set(
        job_id,
        {
            "status": "done",
            "state": {
                "critic_notes": {
                    "fatal_flaws": [
                        {
                            "finding_id": "single-dry-f1",
                            "code": "TOC_SCHEMA_INVALID",
                            "severity": "high",
                            "section": "toc",
                            "status": "open",
                            "message": "ToC invalid.",
                            "source": "rules",
                        }
                    ]
                }
            },
        },
    )

    dry_resp = client.post(
        f"/status/{job_id}/critic/findings/single-dry-f1/ack",
        params={"dry_run": "true"},
        headers={"X-Reviewer": "qa_reviewer"},
    )
    assert dry_resp.status_code == 200
    dry_body = dry_resp.json()
    assert dry_body["status"] == "acknowledged"
    assert dry_body["dry_run"] is True
    assert dry_body["persisted"] is False
    assert dry_body["changed"] is True
    assert dry_body["updated_by"] == "qa_reviewer"

    critic_after_dry = client.get(f"/status/{job_id}/critic")
    assert critic_after_dry.status_code == 200
    flaw_after_dry = critic_after_dry.json()["fatal_flaws"][0]
    assert flaw_after_dry["status"] == "open"

    commit_resp = client.post(
        f"/status/{job_id}/critic/findings/single-dry-f1/ack",
        headers={"X-Reviewer": "qa_reviewer"},
    )
    assert commit_resp.status_code == 200
    commit_body = commit_resp.json()
    assert commit_body["dry_run"] is False
    assert commit_body["persisted"] is True
    assert commit_body["changed"] is True
    assert commit_body["status"] == "acknowledged"

    critic_after_commit = client.get(f"/status/{job_id}/critic")
    assert critic_after_commit.status_code == 200
    flaw_after_commit = critic_after_commit.json()["fatal_flaws"][0]
    assert flaw_after_commit["status"] == "acknowledged"


def test_status_critic_single_status_if_match_returns_conflict_without_changes():
    job_id = "critic-findings-single-if-match-1"
    runtime_state.JOB_STORE.set(
        job_id,
        {
            "status": "done",
            "state": {
                "critic_notes": {
                    "fatal_flaws": [
                        {
                            "finding_id": "single-if-f1",
                            "code": "TOC_SCHEMA_INVALID",
                            "severity": "high",
                            "section": "toc",
                            "status": "acknowledged",
                            "message": "ToC invalid.",
                            "source": "rules",
                        }
                    ]
                }
            },
        },
    )

    conflict_resp = client.post(
        f"/status/{job_id}/critic/findings/single-if-f1/resolve",
        params={"if_match_status": "open"},
        headers={"X-Actor": "qa_reviewer"},
    )
    assert conflict_resp.status_code == 409
    conflict_body = conflict_resp.json()
    detail = conflict_body.get("detail") if isinstance(conflict_body, dict) else {}
    assert detail.get("code") == "finding_status_conflict"
    assert detail.get("if_match_status") == "open"
    assert detail.get("current_status") == "acknowledged"

    critic_after_conflict = client.get(f"/status/{job_id}/critic")
    assert critic_after_conflict.status_code == 200
    flaw_after_conflict = critic_after_conflict.json()["fatal_flaws"][0]
    assert flaw_after_conflict["status"] == "acknowledged"

    commit_resp = client.post(
        f"/status/{job_id}/critic/findings/single-if-f1/resolve",
        params={"if_match_status": "acknowledged"},
        headers={"X-Actor": "qa_reviewer"},
    )
    assert commit_resp.status_code == 200
    commit_body = commit_resp.json()
    assert commit_body["status"] == "resolved"
    assert commit_body["persisted"] is True
    assert commit_body.get("if_match_status") == "acknowledged"


def test_status_critic_single_status_request_id_is_idempotent():
    job_id = "critic-findings-single-request-id-1"
    runtime_state.JOB_STORE.set(
        job_id,
        {
            "status": "done",
            "state": {
                "critic_notes": {
                    "fatal_flaws": [
                        {
                            "finding_id": "single-rid-f1",
                            "code": "TOC_SCHEMA_INVALID",
                            "severity": "high",
                            "section": "toc",
                            "status": "open",
                            "message": "ToC invalid.",
                            "source": "rules",
                        }
                    ]
                }
            },
        },
    )

    first = client.post(
        f"/status/{job_id}/critic/findings/single-rid-f1/ack",
        params={"request_id": "rid-single-1"},
        headers={"X-Actor": "qa_reviewer"},
    )
    assert first.status_code == 200
    first_body = first.json()
    assert first_body["status"] == "acknowledged"
    assert first_body.get("request_id") == "rid-single-1"
    assert first_body.get("idempotent_replay") is not True

    replay = client.post(
        f"/status/{job_id}/critic/findings/single-rid-f1/ack",
        params={"request_id": "rid-single-1"},
        headers={"X-Actor": "qa_reviewer"},
    )
    assert replay.status_code == 200
    replay_body = replay.json()
    assert replay_body["status"] == "acknowledged"
    assert replay_body["request_id"] == "rid-single-1"
    assert replay_body["idempotent_replay"] is True

    mismatch = client.post(
        f"/status/{job_id}/critic/findings/single-rid-f1/resolve",
        params={"request_id": "rid-single-1"},
        headers={"X-Actor": "qa_reviewer"},
    )
    assert mismatch.status_code == 409
    mismatch_detail = mismatch.json().get("detail") or {}
    assert mismatch_detail.get("reason") == "request_id_reused_with_different_payload"

    job = runtime_state.JOB_STORE.get(job_id) or {}
    events = job.get("job_events") if isinstance(job.get("job_events"), list) else []
    finding_events = [e for e in events if isinstance(e, dict) and e.get("type") == "critic_finding_status_changed"]
    assert len(finding_events) == 1


def test_status_critic_supports_legacy_state_critic_fatal_flaws_alias():
    job_id = "critic-findings-legacy-alias-1"
    runtime_state.JOB_STORE.set(
        job_id,
        {
            "status": "done",
            "state": {
                "quality_score": 6.0,
                "critic_score": 6.0,
                "needs_revision": True,
                "critic_fatal_flaws": [
                    {
                        "code": "TOC_SCHEMA_INVALID",
                        "severity": "high",
                        "section": "toc",
                        "message": "Legacy alias finding should still be normalized.",
                        "source": "rules",
                    }
                ],
            },
            "review_comments": [],
        },
    )

    critic_resp = client.get(f"/status/{job_id}/critic")
    assert critic_resp.status_code == 200
    body = critic_resp.json()
    assert body["fatal_flaw_count"] == 1
    flaw = body["fatal_flaws"][0]
    assert flaw["id"] == flaw["finding_id"]
    assert flaw["status"] == "open"

    ack_resp = client.post(f"/status/{job_id}/critic/findings/{flaw['finding_id']}/ack")
    assert ack_resp.status_code == 200
    assert ack_resp.json()["status"] == "acknowledged"
