# grantflow/tests/test_integration_hitl_queue.py

from fastapi.testclient import TestClient

import grantflow.api.review_service as review_service
from grantflow.api import runtime_state
from grantflow.api.app import app
from grantflow.tests.integration_helpers import wait_for_terminal_status

client = TestClient(app)


def test_generate_dispatches_webhook_events(monkeypatch):
    events = []

    def fake_send_job_webhook_event(**kwargs):
        events.append(kwargs)

    monkeypatch.setattr(review_service, "send_job_webhook_event", fake_send_job_webhook_event)

    response = client.post(
        "/generate",
        json={
            "donor_id": "usaid",
            "input_context": {"project": "Livelihoods", "country": "Kenya"},
            "llm_mode": False,
            "hitl_enabled": False,
            "webhook_url": "https://example.com/webhook",
            "webhook_secret": "secret123",
        },
    )
    assert response.status_code == 200
    job_id = response.json()["job_id"]

    status = wait_for_terminal_status(client, job_id)
    assert status["status"] == "done"

    event_names = [evt["event"] for evt in events]
    assert "job.started" in event_names
    assert "job.completed" in event_names

    completed = [evt for evt in events if evt["event"] == "job.completed"][-1]
    assert completed["job_id"] == job_id
    assert completed["url"] == "https://example.com/webhook"
    assert completed["secret"] == "secret123"
    assert completed["job"]["status"] == "done"
    assert completed["job"]["webhook_configured"] is True
    assert "webhook_url" not in completed["job"]
    assert "webhook_secret" not in completed["job"]


def test_generate_request_id_is_idempotent():
    first = client.post(
        "/generate",
        json={
            "donor_id": "usaid",
            "input_context": {"project": "Idempotent generation", "country": "Kenya"},
            "llm_mode": False,
            "hitl_enabled": False,
            "request_id": "rid-generate-1",
        },
    )
    assert first.status_code == 200
    first_body = first.json()
    assert first_body["request_id"] == "rid-generate-1"
    assert first_body.get("idempotent_replay") is not True

    replay = client.post(
        "/generate",
        json={
            "donor_id": "usaid",
            "input_context": {"project": "Idempotent generation", "country": "Kenya"},
            "llm_mode": False,
            "hitl_enabled": False,
            "request_id": "rid-generate-1",
        },
    )
    assert replay.status_code == 200
    replay_body = replay.json()
    assert replay_body["request_id"] == "rid-generate-1"
    assert replay_body["idempotent_replay"] is True
    assert replay_body["job_id"] == first_body["job_id"]

    mismatch = client.post(
        "/generate",
        json={
            "donor_id": "usaid",
            "input_context": {"project": "Different payload", "country": "Kenya"},
            "llm_mode": False,
            "hitl_enabled": False,
            "request_id": "rid-generate-1",
        },
    )
    assert mismatch.status_code == 409
    mismatch_detail = mismatch.json().get("detail") or {}
    assert mismatch_detail.get("reason") == "request_id_reused_with_different_payload"


def test_cancel_request_id_is_idempotent():
    job_id = "cancel-request-id-job-1"
    runtime_state.JOB_STORE.set(
        job_id,
        {
            "status": "running",
            "state": {"donor_id": "usaid", "input_context": {"project": "Cancel idem", "country": "Kenya"}},
            "hitl_enabled": False,
        },
    )

    first = client.post(f"/cancel/{job_id}", params={"request_id": "rid-cancel-1"})
    assert first.status_code == 200
    first_body = first.json()
    assert first_body["status"] == "canceled"
    assert first_body["request_id"] == "rid-cancel-1"
    assert first_body.get("idempotent_replay") is not True

    replay = client.post(f"/cancel/{job_id}", params={"request_id": "rid-cancel-1"})
    assert replay.status_code == 200
    replay_body = replay.json()
    assert replay_body["status"] == "canceled"
    assert replay_body["request_id"] == "rid-cancel-1"
    assert replay_body["idempotent_replay"] is True

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


def test_cancel_pending_hitl_job_and_cleanup_checkpoint(monkeypatch):
    events = []

    def fake_send_job_webhook_event(**kwargs):
        events.append(kwargs)

    monkeypatch.setattr(review_service, "send_job_webhook_event", fake_send_job_webhook_event)

    response = client.post(
        "/generate",
        json={
            "donor_id": "usaid",
            "input_context": {"project": "Education", "country": "Kenya"},
            "llm_mode": False,
            "hitl_enabled": True,
            "webhook_url": "https://example.com/webhook",
        },
    )
    assert response.status_code == 200
    job_id = response.json()["job_id"]

    status = wait_for_terminal_status(client, job_id)
    assert status["status"] == "pending_hitl"
    checkpoint_id = status["checkpoint_id"]

    cancel = client.post(f"/cancel/{job_id}")
    assert cancel.status_code == 200
    assert cancel.json()["status"] == "canceled"
    assert cancel.json()["previous_status"] == "pending_hitl"

    status_after = client.get(f"/status/{job_id}")
    assert status_after.status_code == 200
    body = status_after.json()
    assert body["status"] == "canceled"
    assert body["webhook_configured"] is True
    assert "webhook_url" not in body
    assert "webhook_secret" not in body

    checkpoint = runtime_state.hitl_manager.get_checkpoint(checkpoint_id)
    assert checkpoint is not None
    cp_status = checkpoint["status"]
    assert getattr(cp_status, "value", cp_status) == "canceled"

    pending = client.get("/hitl/pending")
    assert pending.status_code == 200
    assert all(cp["id"] != checkpoint_id for cp in pending.json()["checkpoints"])

    event_names = [evt["event"] for evt in events]
    assert "job.pending_hitl" in event_names
    assert "job.canceled" in event_names
