# grantflow/tests/test_integration_readiness_dispatcher.py

from fastapi.testclient import TestClient

import grantflow.api.readiness_service as readiness_service
from grantflow.api import runtime_state
from grantflow.api.app import app
from grantflow.core.config import config
from grantflow.memory_bank.vector_store import vector_store

client = TestClient(app)


def test_ready_endpoint_redis_dispatcher_mode_without_local_consumer(monkeypatch):
    monkeypatch.setattr(config.job_runner, "mode", "redis_queue")
    monkeypatch.setattr(config.job_runner, "redis_worker_heartbeat_policy_mode", "strict")
    monkeypatch.setattr(
        runtime_state,
        "JOB_RUNNER",
        type(
            "_Runner",
            (),
            {
                "diagnostics": staticmethod(
                    lambda: {
                        "backend": "redis",
                        "consumer_enabled": False,
                        "running": False,
                        "redis_available": True,
                        "queue_size": 0,
                        "worker_heartbeat": {"present": True, "healthy": True, "age_seconds": 1.2},
                    }
                )
            },
        )(),
    )
    monkeypatch.setattr(readiness_service, "_vector_store_readiness", lambda: {"ready": True, "backend": "memory"})

    response = client.get("/ready")
    assert response.status_code == 200
    body = response.json()
    assert body["status"] == "ready"
    checks = body["checks"]
    assert checks["job_runner"]["mode"] == "redis_queue"
    assert checks["job_runner"]["ready"] is True


def test_ready_endpoint_degrades_when_dispatcher_worker_heartbeat_missing(monkeypatch):
    monkeypatch.setattr(config.job_runner, "mode", "redis_queue")
    monkeypatch.setattr(config.job_runner, "redis_worker_heartbeat_policy_mode", "strict")
    monkeypatch.setattr(
        runtime_state,
        "JOB_RUNNER",
        type(
            "_Runner",
            (),
            {
                "diagnostics": staticmethod(
                    lambda: {
                        "backend": "redis",
                        "consumer_enabled": False,
                        "running": False,
                        "redis_available": True,
                        "queue_size": 0,
                        "worker_heartbeat": {"present": False, "healthy": False},
                    }
                )
            },
        )(),
    )
    monkeypatch.setattr(readiness_service, "_vector_store_readiness", lambda: {"ready": True, "backend": "memory"})

    response = client.get("/ready")
    assert response.status_code == 503
    detail = response.json()["detail"]
    assert detail["status"] == "degraded"
    checks = detail["checks"]
    assert checks["job_runner"]["ready"] is False
    alerts = checks["job_runner"]["alerts"]
    assert any(
        str(item.get("code") or "") == "REDIS_DISPATCHER_WORKER_HEARTBEAT_MISSING"
        for item in alerts
        if isinstance(item, dict)
    )


def test_ready_endpoint_warn_mode_allows_missing_dispatcher_heartbeat(monkeypatch):
    monkeypatch.setattr(config.job_runner, "mode", "redis_queue")
    monkeypatch.setattr(config.job_runner, "redis_worker_heartbeat_policy_mode", "warn")
    monkeypatch.setattr(
        runtime_state,
        "JOB_RUNNER",
        type(
            "_Runner",
            (),
            {
                "diagnostics": staticmethod(
                    lambda: {
                        "backend": "redis",
                        "consumer_enabled": False,
                        "running": False,
                        "redis_available": True,
                        "queue_size": 0,
                        "worker_heartbeat": {"present": False, "healthy": False},
                    }
                )
            },
        )(),
    )
    monkeypatch.setattr(readiness_service, "_vector_store_readiness", lambda: {"ready": True, "backend": "memory"})

    response = client.get("/ready")
    assert response.status_code == 200
    body = response.json()
    checks = body["checks"]
    assert checks["job_runner"]["ready"] is True
    assert checks["job_runner"]["dispatcher_worker_heartbeat_policy"]["mode"] == "warn"
    alerts = checks["job_runner"]["alerts"]
    alert = next(
        (
            item
            for item in alerts
            if isinstance(item, dict) and str(item.get("code") or "") == "REDIS_DISPATCHER_WORKER_HEARTBEAT_MISSING"
        ),
        None,
    )
    assert isinstance(alert, dict)
    assert alert["blocking"] is False


def test_ready_endpoint_reports_dead_letter_alert_when_threshold_exceeded(monkeypatch):
    monkeypatch.setattr(config.job_runner, "mode", "redis_queue")
    monkeypatch.setattr(config.job_runner, "dead_letter_alert_threshold", 1)
    monkeypatch.setattr(config.job_runner, "dead_letter_alert_blocking", False)
    monkeypatch.setattr(
        runtime_state,
        "JOB_RUNNER",
        type(
            "_Runner",
            (),
            {
                "diagnostics": staticmethod(
                    lambda: {
                        "backend": "redis",
                        "consumer_enabled": False,
                        "running": False,
                        "redis_available": True,
                        "queue_size": 0,
                        "dead_letter_queue_size": 3,
                        "worker_heartbeat": {"present": True, "healthy": True, "age_seconds": 0.8},
                    }
                )
            },
        )(),
    )
    monkeypatch.setattr(readiness_service, "_vector_store_readiness", lambda: {"ready": True, "backend": "memory"})

    response = client.get("/ready")
    assert response.status_code == 200
    body = response.json()
    checks = body["checks"]
    assert checks["job_runner"]["ready"] is True
    alert = checks["job_runner"]["dead_letter_alert"]
    assert alert["triggered"] is True
    assert alert["blocking"] is False
    alerts = checks["job_runner"]["alerts"]
    assert alerts and alerts[0]["code"] == "DEAD_LETTER_QUEUE_THRESHOLD_EXCEEDED"


def test_ready_endpoint_can_block_when_dead_letter_alert_is_blocking(monkeypatch):
    monkeypatch.setattr(config.job_runner, "mode", "redis_queue")
    monkeypatch.setattr(config.job_runner, "dead_letter_alert_threshold", 1)
    monkeypatch.setattr(config.job_runner, "dead_letter_alert_blocking", True)
    monkeypatch.setattr(
        runtime_state,
        "JOB_RUNNER",
        type(
            "_Runner",
            (),
            {
                "diagnostics": staticmethod(
                    lambda: {
                        "backend": "redis",
                        "consumer_enabled": False,
                        "running": False,
                        "redis_available": True,
                        "queue_size": 0,
                        "dead_letter_queue_size": 2,
                        "worker_heartbeat": {"present": True, "healthy": True, "age_seconds": 0.8},
                    }
                )
            },
        )(),
    )
    monkeypatch.setattr(readiness_service, "_vector_store_readiness", lambda: {"ready": True, "backend": "memory"})

    response = client.get("/ready")
    assert response.status_code == 503
    detail = response.json()["detail"]
    assert detail["status"] == "degraded"
    checks = detail["checks"]
    assert checks["job_runner"]["ready"] is False
    alert = checks["job_runner"]["dead_letter_alert"]
    assert alert["triggered"] is True
    assert alert["blocking"] is True


def test_ready_endpoint_returns_503_when_vector_store_unavailable(monkeypatch):
    class BrokenClient:
        def heartbeat(self):
            raise RuntimeError("chroma unavailable")

    monkeypatch.setattr(vector_store, "client", BrokenClient())

    response = client.get("/ready")
    assert response.status_code == 503
    body = response.json()
    assert body["detail"]["status"] == "degraded"
    assert body["detail"]["checks"]["vector_store"]["backend"] == "chroma"
    assert body["detail"]["checks"]["vector_store"]["ready"] is False
    assert "chroma unavailable" in body["detail"]["checks"]["vector_store"]["error"]
    preflight_policy = body["detail"]["checks"]["preflight_grounding_policy"]
    assert preflight_policy["mode"] in {"warn", "strict", "off"}
    runtime_gate = body["detail"]["checks"]["runtime_grounded_quality_gate"]
    assert runtime_gate["mode"] in {"warn", "strict", "off"}
    mel_policy = body["detail"]["checks"]["mel_grounding_policy"]
    assert mel_policy["mode"] in {"warn", "strict", "off"}
    export_policy = body["detail"]["checks"]["export_grounding_policy"]
    assert export_policy["mode"] in {"warn", "strict", "off"}
    export_contract_policy = body["detail"]["checks"]["export_contract_policy"]
    assert export_contract_policy["mode"] in {"warn", "strict", "off"}
    export_runtime_gate_policy = body["detail"]["checks"]["export_runtime_grounded_gate_policy"]
    assert isinstance(export_runtime_gate_policy["require_pass"], bool)
