# grantflow/tests/test_integration_review_workflow_sla_exports.py

import gzip

import json


from fastapi.testclient import TestClient


from grantflow.api import runtime_state

from grantflow.api.app import app

client = TestClient(app)


def test_status_review_workflow_sla_hotspots_export_supports_csv_json_and_gzip():
    job_id = "review-workflow-sla-hotspots-export-job-1"
    runtime_state.JOB_STORE.set(
        job_id,
        {
            "status": "done",
            "state": {
                "critic_notes": {
                    "fatal_flaws": [
                        {
                            "finding_id": "finding-h-exp-1",
                            "code": "TOC_SCHEMA_INVALID",
                            "severity": "high",
                            "section": "toc",
                            "status": "open",
                            "message": "ToC mismatch.",
                            "updated_at": "2026-02-27T14:00:00+00:00",
                            "due_at": "2026-02-27T08:00:00+00:00",
                            "sla_hours": 24,
                        },
                        {
                            "finding_id": "finding-h-exp-2",
                            "code": "MEL_BASELINE_MISSING",
                            "severity": "medium",
                            "section": "logframe",
                            "status": "acknowledged",
                            "message": "Baseline missing.",
                            "updated_at": "2026-02-27T14:30:00+00:00",
                            "due_at": "2026-02-26T08:00:00+00:00",
                            "sla_hours": 72,
                        },
                    ]
                }
            },
            "review_comments": [
                {
                    "comment_id": "comment-h-exp-1",
                    "ts": "2026-02-27T13:00:00+00:00",
                    "section": "toc",
                    "status": "open",
                    "message": "Need stronger assumptions.",
                    "linked_finding_id": "finding-h-exp-1",
                    "due_at": "2026-02-27T09:00:00+00:00",
                    "sla_hours": 24,
                },
                {
                    "comment_id": "comment-h-exp-2",
                    "ts": "2026-02-27T13:05:00+00:00",
                    "section": "logframe",
                    "status": "open",
                    "message": "Indicator wording update pending.",
                    "linked_finding_id": "finding-h-exp-2",
                    "due_at": "2026-02-26T09:00:00+00:00",
                    "sla_hours": 72,
                },
            ],
            "job_events": [
                {
                    "event_id": "rwf-sla-hotspots-exp-1",
                    "ts": "2026-02-27T15:00:00+00:00",
                    "type": "critic_finding_status_changed",
                    "finding_id": "finding-h-exp-2",
                    "status": "acknowledged",
                    "section": "logframe",
                    "severity": "medium",
                }
            ],
        },
    )

    csv_resp = client.get(
        f"/status/{job_id}/review/workflow/sla/hotspots/export",
        params={"hotspot_kind": "finding", "overdue_after_hours": 2, "format": "csv"},
    )
    assert csv_resp.status_code == 200
    assert csv_resp.headers["content-type"].startswith("text/csv")
    csv_disposition = csv_resp.headers.get("content-disposition", "")
    assert f"grantflow_review_workflow_sla_hotspots_{job_id}.csv" in csv_disposition
    csv_text = csv_resp.text
    assert csv_text.startswith("field,value\n")
    assert "filters.hotspot_kind,finding" in csv_text
    assert "filters.overdue_after_hours,2" in csv_text
    assert "total_overdue_items,2" in csv_text

    json_resp = client.get(
        f"/status/{job_id}/review/workflow/sla/hotspots/trends/export",
        params={"hotspot_severity": "high", "overdue_after_hours": 2, "format": "json"},
    )
    assert json_resp.status_code == 200
    assert json_resp.headers["content-type"].startswith("application/json")
    json_payload = json_resp.json()
    assert json_payload["filters"]["hotspot_severity"] == "high"
    assert json_payload["hotspot_count_total"] == 2
    assert json_payload["bucket_count"] == 1

    gzip_resp = client.get(
        f"/status/{job_id}/review/workflow/sla/hotspots/trends/export",
        params={"overdue_after_hours": 2, "format": "json", "gzip": "true"},
    )
    assert gzip_resp.status_code == 200
    assert gzip_resp.headers["content-type"].startswith("application/gzip")
    gzip_disposition = gzip_resp.headers.get("content-disposition", "")
    assert f"grantflow_review_workflow_sla_hotspots_trends_{job_id}.json.gz" in gzip_disposition
    gzip_payload = json.loads(gzip.decompress(gzip_resp.content).decode("utf-8"))
    assert gzip_payload["bucket_count"] == 2
    assert gzip_payload["hotspot_count_total"] == 4


def test_status_review_workflow_sla_trends_export_supports_csv_json_and_gzip():
    job_id = "review-workflow-sla-trends-export-job-1"
    runtime_state.JOB_STORE.set(
        job_id,
        {
            "status": "done",
            "state": {
                "critic_notes": {
                    "fatal_flaws": [
                        {
                            "finding_id": "finding-t-exp-1",
                            "code": "TOC_SCHEMA_INVALID",
                            "severity": "high",
                            "section": "toc",
                            "status": "open",
                            "message": "ToC mismatch.",
                            "updated_at": "2026-02-27T14:00:00+00:00",
                            "due_at": "2026-02-27T08:00:00+00:00",
                            "sla_hours": 24,
                        },
                        {
                            "finding_id": "finding-t-exp-2",
                            "code": "MEL_BASELINE_MISSING",
                            "severity": "medium",
                            "section": "logframe",
                            "status": "acknowledged",
                            "message": "Baseline missing.",
                            "updated_at": "2026-02-27T14:30:00+00:00",
                            "due_at": "2026-02-26T08:00:00+00:00",
                            "sla_hours": 72,
                        },
                    ]
                }
            },
            "review_comments": [
                {
                    "comment_id": "comment-t-exp-1",
                    "ts": "2026-02-27T13:00:00+00:00",
                    "section": "toc",
                    "status": "open",
                    "message": "Need stronger assumptions.",
                    "linked_finding_id": "finding-t-exp-1",
                    "due_at": "2026-02-27T09:00:00+00:00",
                    "sla_hours": 24,
                },
                {
                    "comment_id": "comment-t-exp-2",
                    "ts": "2026-02-27T13:05:00+00:00",
                    "section": "logframe",
                    "status": "open",
                    "message": "Indicator wording update pending.",
                    "linked_finding_id": "finding-t-exp-2",
                    "due_at": "2026-02-26T09:00:00+00:00",
                    "sla_hours": 72,
                },
            ],
            "job_events": [
                {
                    "event_id": "rwf-sla-trend-exp-1",
                    "ts": "2026-02-27T15:00:00+00:00",
                    "type": "critic_finding_status_changed",
                    "finding_id": "finding-t-exp-2",
                    "status": "acknowledged",
                    "section": "logframe",
                    "severity": "medium",
                }
            ],
        },
    )

    csv_resp = client.get(
        f"/status/{job_id}/review/workflow/sla/trends/export",
        params={"finding_section": "toc", "overdue_after_hours": 2, "format": "csv"},
    )
    assert csv_resp.status_code == 200
    assert csv_resp.headers["content-type"].startswith("text/csv")
    csv_disposition = csv_resp.headers.get("content-disposition", "")
    assert f"grantflow_review_workflow_sla_trends_{job_id}.csv" in csv_disposition
    csv_text = csv_resp.text
    assert csv_text.startswith("field,value\n")
    assert "filters.finding_section,toc" in csv_text
    assert "filters.overdue_after_hours,2" in csv_text
    assert "overdue_total,2" in csv_text
    assert "total_series[0].bucket,2026-02-27" in csv_text

    json_resp = client.get(
        f"/status/{job_id}/review/workflow/sla/trends/export",
        params={"finding_code": "MEL_BASELINE_MISSING", "overdue_after_hours": 2, "format": "json"},
    )
    assert json_resp.status_code == 200
    assert json_resp.headers["content-type"].startswith("application/json")
    json_payload = json_resp.json()
    assert json_payload["filters"]["finding_code"] == "MEL_BASELINE_MISSING"
    assert json_payload["bucket_count"] == 1
    assert json_payload["overdue_total"] == 2

    gzip_resp = client.get(
        f"/status/{job_id}/review/workflow/sla/trends/export",
        params={"overdue_after_hours": 2, "format": "json", "gzip": "true"},
    )
    assert gzip_resp.status_code == 200
    assert gzip_resp.headers["content-type"].startswith("application/gzip")
    gzip_disposition = gzip_resp.headers.get("content-disposition", "")
    assert f"grantflow_review_workflow_sla_trends_{job_id}.json.gz" in gzip_disposition
    gzip_payload = json.loads(gzip.decompress(gzip_resp.content).decode("utf-8"))
    assert gzip_payload["bucket_count"] == 2
    assert gzip_payload["overdue_total"] == 4


def test_status_review_workflow_sla_export_supports_csv_json_and_gzip():
    job_id = "review-workflow-sla-export-job-1"
    runtime_state.JOB_STORE.set(
        job_id,
        {
            "status": "done",
            "state": {
                "critic_notes": {
                    "fatal_flaws": [
                        {
                            "finding_id": "finding-sla-exp-1",
                            "code": "TOC_SCHEMA_INVALID",
                            "severity": "high",
                            "section": "toc",
                            "status": "open",
                            "message": "ToC mismatch.",
                            "updated_at": "2026-02-27T07:00:00+00:00",
                            "due_at": "2026-02-27T08:00:00+00:00",
                            "sla_hours": 24,
                        },
                        {
                            "finding_id": "finding-sla-exp-2",
                            "code": "MEL_BASELINE_MISSING",
                            "severity": "medium",
                            "section": "logframe",
                            "status": "acknowledged",
                            "message": "Baseline missing.",
                            "acknowledged_at": "2026-02-27T09:30:00+00:00",
                            "due_at": "2026-02-27T12:00:00+00:00",
                            "sla_hours": 72,
                        },
                    ]
                }
            },
            "review_comments": [
                {
                    "comment_id": "comment-sla-exp-1",
                    "ts": "2026-02-27T06:30:00+00:00",
                    "section": "toc",
                    "status": "open",
                    "message": "Need stronger assumptions.",
                    "linked_finding_id": "finding-sla-exp-1",
                    "due_at": "2026-02-27T09:00:00+00:00",
                    "sla_hours": 24,
                },
                {
                    "comment_id": "comment-sla-exp-2",
                    "ts": "2026-02-27T10:20:00+00:00",
                    "section": "logframe",
                    "status": "resolved",
                    "message": "Resolved in reviewer pass.",
                    "linked_finding_id": "finding-sla-exp-2",
                    "due_at": "2026-02-27T12:30:00+00:00",
                    "sla_hours": 72,
                },
            ],
            "job_events": [
                {
                    "event_id": "rwf-sla-exp-1",
                    "ts": "2026-02-27T07:00:00+00:00",
                    "type": "critic_finding_status_changed",
                    "finding_id": "finding-sla-exp-1",
                    "status": "open",
                    "section": "toc",
                    "severity": "high",
                },
                {
                    "event_id": "rwf-sla-exp-2",
                    "ts": "2026-02-27T09:30:00+00:00",
                    "type": "critic_finding_status_changed",
                    "finding_id": "finding-sla-exp-2",
                    "status": "acknowledged",
                    "section": "logframe",
                    "severity": "medium",
                },
            ],
        },
    )

    csv_resp = client.get(
        f"/status/{job_id}/review/workflow/sla/export",
        params={
            "finding_section": "toc",
            "finding_code": "toc_schema_invalid",
            "overdue_after_hours": 2,
            "format": "csv",
        },
    )
    assert csv_resp.status_code == 200
    assert csv_resp.headers["content-type"].startswith("text/csv")
    csv_disposition = csv_resp.headers.get("content-disposition", "")
    assert f"grantflow_review_workflow_sla_{job_id}.csv" in csv_disposition
    csv_text = csv_resp.text
    assert csv_text.startswith("field,value\n")
    assert "filters.finding_section,toc" in csv_text
    assert "filters.finding_code,TOC_SCHEMA_INVALID" in csv_text
    assert "filters.overdue_after_hours,2" in csv_text
    assert "overdue_total,2" in csv_text

    json_resp = client.get(
        f"/status/{job_id}/review/workflow/sla/export",
        params={"finding_section": "logframe", "overdue_after_hours": 2, "format": "json"},
    )
    assert json_resp.status_code == 200
    assert json_resp.headers["content-type"].startswith("application/json")
    json_payload = json_resp.json()
    assert json_payload["filters"]["finding_section"] == "logframe"
    assert json_payload["filters"]["overdue_after_hours"] == 2
    assert json_payload["finding_total"] == 1
    assert json_payload["comment_total"] == 1
    assert json_payload["overdue_total"] == 0

    gzip_resp = client.get(
        f"/status/{job_id}/review/workflow/sla/export",
        params={"finding_code": "MEL_BASELINE_MISSING", "overdue_after_hours": 2, "format": "json", "gzip": "true"},
    )
    assert gzip_resp.status_code == 200
    assert gzip_resp.headers["content-type"].startswith("application/gzip")
    gzip_disposition = gzip_resp.headers.get("content-disposition", "")
    assert f"grantflow_review_workflow_sla_{job_id}.json.gz" in gzip_disposition
    gzip_payload = json.loads(gzip.decompress(gzip_resp.content).decode("utf-8"))
    assert gzip_payload["filters"]["finding_code"] == "MEL_BASELINE_MISSING"
    assert gzip_payload["finding_total"] == 1
