// tests/startup_gate_tests.rs

//! Tests for SYS-0024: Startup mutation gate.
//!
//! While the server is initialising, all mutation operations on queues must
//! be rejected with 503 + Retry-After header + JSON body instructing the
//! caller to retry.  Read-only endpoints must remain accessible.

use axum::{
    body::Body,
    http::{Request, StatusCode},
};
use qrusty::api::ApiServer;
use qrusty::storage::Storage;
use serde_json::{json, Value};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use tempfile::TempDir;
use tower::ServiceExt;

/// Creates a test API server that is stuck in the "initialising" state.
async fn create_initializing_api() -> (ApiServer, TempDir) {
    let temp_dir = TempDir::new().expect("Failed to create temp directory");
    let storage =
        Storage::new(temp_dir.path().to_str().unwrap()).expect("Failed to create test storage");
    let flag = Arc::new(AtomicBool::new(true));
    let api = ApiServer::new(Arc::new(storage)).with_initializing(flag);
    (api, temp_dir)
}

/// Creates a test API server that is fully ready (not initialising).
async fn create_ready_api() -> (ApiServer, TempDir) {
    let temp_dir = TempDir::new().expect("Failed to create temp directory");
    let storage =
        Storage::new(temp_dir.path().to_str().unwrap()).expect("Failed to create test storage");
    let api = ApiServer::new(Arc::new(storage));
    (api, temp_dir)
}

/// Helper: send a request and return status, body, and the full response headers.
async fn make_request_full(
    api: ApiServer,
    method: &str,
    path: &str,
    body: Option<Value>,
) -> (StatusCode, Value, axum::http::HeaderMap) {
    let app = api.router();

    let request_builder = Request::builder()
        .method(method)
        .uri(path)
        .header("content-type", "application/json");

    let request = if let Some(body_json) = body {
        request_builder
            .body(Body::from(body_json.to_string()))
            .unwrap()
    } else {
        request_builder.body(Body::empty()).unwrap()
    };

    let response = app.oneshot(request).await.unwrap();
    let status = response.status();
    let headers = response.headers().clone();

    let body = axum::body::to_bytes(response.into_body(), usize::MAX)
        .await
        .unwrap();
    let body_str = String::from_utf8(body.to_vec()).unwrap();

    let json_body: Value = if body_str.is_empty() {
        Value::Null
    } else {
        serde_json::from_str(&body_str).unwrap_or(Value::String(body_str))
    };

    (status, json_body, headers)
}

// ---------------------------------------------------------------------------
// Mutation endpoints BLOCKED during init (SYS-0024 §1)
// ---------------------------------------------------------------------------

/// Verifies: SYS-0024 §1 — POST /create-queue blocked during init
#[tokio::test]
async fn test_create_queue_blocked_during_init() {
    let (api, _dir) = create_initializing_api().await;
    let body = json!({"name": "test-q", "ordering": "MaxFirst"});
    let (status, json, headers) = make_request_full(api, "POST", "/create-queue", Some(body)).await;

    assert_eq!(status, StatusCode::SERVICE_UNAVAILABLE);
    assert_eq!(json["status"], "starting");
    assert!(json["message"].as_str().unwrap().contains("initialising"));
    assert_eq!(json["retry_after_secs"], 2);
    assert_eq!(headers.get("retry-after").unwrap(), "2");
}

/// Verifies: SYS-0024 §1 — POST /update-queue blocked during init
#[tokio::test]
async fn test_update_queue_blocked_during_init() {
    let (api, _dir) = create_initializing_api().await;
    let body = json!({"name": "test-q", "new_name": "renamed"});
    let (status, json, headers) = make_request_full(api, "POST", "/update-queue", Some(body)).await;

    assert_eq!(status, StatusCode::SERVICE_UNAVAILABLE);
    assert_eq!(json["status"], "starting");
    assert_eq!(headers.get("retry-after").unwrap(), "2");
}

/// Verifies: SYS-0024 §1 — DELETE /delete-queue/{queue} blocked during init
#[tokio::test]
async fn test_delete_queue_blocked_during_init() {
    let (api, _dir) = create_initializing_api().await;
    let (status, json, headers) =
        make_request_full(api, "DELETE", "/delete-queue/test-q", None).await;

    assert_eq!(status, StatusCode::SERVICE_UNAVAILABLE);
    assert_eq!(json["status"], "starting");
    assert_eq!(headers.get("retry-after").unwrap(), "2");
}

/// Verifies: SYS-0024 §1 — POST /purge-queue/{queue} blocked during init
#[tokio::test]
async fn test_purge_queue_blocked_during_init() {
    let (api, _dir) = create_initializing_api().await;
    let (status, json, headers) = make_request_full(api, "POST", "/purge-queue/test-q", None).await;

    assert_eq!(status, StatusCode::SERVICE_UNAVAILABLE);
    assert_eq!(json["status"], "starting");
    assert_eq!(headers.get("retry-after").unwrap(), "2");
}

/// Verifies: SYS-0024 §1 — POST /purge-all blocked during init
#[tokio::test]
async fn test_purge_all_blocked_during_init() {
    let (api, _dir) = create_initializing_api().await;
    let (status, json, headers) = make_request_full(api, "POST", "/purge-all", None).await;

    assert_eq!(status, StatusCode::SERVICE_UNAVAILABLE);
    assert_eq!(json["status"], "starting");
    assert_eq!(headers.get("retry-after").unwrap(), "2");
}

/// Verifies: SYS-0024 §1 — DELETE /delete-all blocked during init
#[tokio::test]
async fn test_delete_all_blocked_during_init() {
    let (api, _dir) = create_initializing_api().await;
    let (status, json, headers) = make_request_full(api, "POST", "/delete-all", None).await;

    assert_eq!(status, StatusCode::SERVICE_UNAVAILABLE);
    assert_eq!(json["status"], "starting");
    assert_eq!(headers.get("retry-after").unwrap(), "2");
}

/// Verifies: SYS-0024 §1 — POST /publish blocked during init
#[tokio::test]
async fn test_publish_blocked_during_init() {
    let (api, _dir) = create_initializing_api().await;
    let body = json!({"queue": "test-q", "priority": 1, "payload": "{}"});
    let (status, json, headers) = make_request_full(api, "POST", "/publish", Some(body)).await;

    assert_eq!(status, StatusCode::SERVICE_UNAVAILABLE);
    assert_eq!(json["status"], "starting");
    assert!(json["message"].as_str().unwrap().contains("initialising"));
    assert_eq!(json["retry_after_secs"], 2);
    assert_eq!(headers.get("retry-after").unwrap(), "2");
}

/// Verifies: SYS-0024 §1 — POST /consume/{queue} blocked during init
#[tokio::test]
async fn test_consume_blocked_during_init() {
    let (api, _dir) = create_initializing_api().await;
    let (status, json, headers) = make_request_full(api, "POST", "/consume/test-q", None).await;

    assert_eq!(status, StatusCode::SERVICE_UNAVAILABLE);
    assert_eq!(json["status"], "starting");
    assert_eq!(headers.get("retry-after").unwrap(), "2");
}

/// Verifies: SYS-0024 §1 — POST /ack/{queue}/{id} blocked during init
#[tokio::test]
async fn test_ack_blocked_during_init() {
    let (api, _dir) = create_initializing_api().await;
    let body = json!({"consumer_id": "c1"});
    let (status, json, headers) =
        make_request_full(api, "POST", "/ack/test-q/msg-1", Some(body)).await;

    assert_eq!(status, StatusCode::SERVICE_UNAVAILABLE);
    assert_eq!(json["status"], "starting");
    assert_eq!(headers.get("retry-after").unwrap(), "2");
}

/// Verifies: SYS-0024 §1 — POST /nack/{queue}/{id} blocked during init
#[tokio::test]
async fn test_nack_blocked_during_init() {
    let (api, _dir) = create_initializing_api().await;
    let body = json!({"consumer_id": "c1"});
    let (status, json, headers) =
        make_request_full(api, "POST", "/nack/test-q/msg-1", Some(body)).await;

    assert_eq!(status, StatusCode::SERVICE_UNAVAILABLE);
    assert_eq!(json["status"], "starting");
    assert_eq!(headers.get("retry-after").unwrap(), "2");
}

/// Verifies: SYS-0024 §1 — POST /ack-batch/{queue} blocked during init
#[tokio::test]
async fn test_ack_batch_blocked_during_init() {
    let (api, _dir) = create_initializing_api().await;
    let body = json!({"ids": ["msg-1"], "consumer_id": "c1"});
    let (status, json, headers) =
        make_request_full(api, "POST", "/ack-batch/test-q", Some(body)).await;

    assert_eq!(status, StatusCode::SERVICE_UNAVAILABLE);
    assert_eq!(json["status"], "starting");
    assert_eq!(headers.get("retry-after").unwrap(), "2");
}

/// Verifies: SYS-0024 §1 — POST /nack-batch/{queue} blocked during init
#[tokio::test]
async fn test_nack_batch_blocked_during_init() {
    let (api, _dir) = create_initializing_api().await;
    let body = json!({"ids": ["msg-1"], "consumer_id": "c1"});
    let (status, json, headers) =
        make_request_full(api, "POST", "/nack-batch/test-q", Some(body)).await;

    assert_eq!(status, StatusCode::SERVICE_UNAVAILABLE);
    assert_eq!(json["status"], "starting");
    assert_eq!(headers.get("retry-after").unwrap(), "2");
}

// ---------------------------------------------------------------------------
// Read-only endpoints ALLOWED during init (SYS-0024 §3)
// ---------------------------------------------------------------------------

/// Verifies: SYS-0024 §3 — GET /health accessible during init
#[tokio::test]
async fn test_health_allowed_during_init() {
    let (api, _dir) = create_initializing_api().await;
    let (status, json, _headers) = make_request_full(api, "GET", "/health", None).await;

    assert_eq!(status, StatusCode::OK);
    assert_eq!(json["status"], "starting");
}

/// Verifies: SYS-0024 §3 — GET /stats accessible during init
#[tokio::test]
async fn test_stats_allowed_during_init() {
    let (api, _dir) = create_initializing_api().await;
    let (status, _json, _headers) = make_request_full(api, "GET", "/stats", None).await;

    assert_eq!(status, StatusCode::OK);
}

/// Verifies: SYS-0024 §3 — GET /queues accessible during init
#[tokio::test]
async fn test_queues_allowed_during_init() {
    let (api, _dir) = create_initializing_api().await;
    let (status, _json, _headers) = make_request_full(api, "GET", "/queues", None).await;

    assert_eq!(status, StatusCode::OK);
}

/// Verifies: SYS-0024 §3 — GET /queue-stats/{queue} accessible during init
#[tokio::test]
async fn test_queue_stats_allowed_during_init() {
    let (api, _dir) = create_initializing_api().await;
    let (status, _json, _headers) =
        make_request_full(api, "GET", "/queue-stats/test-q", None).await;

    // 404 is acceptable — the queue doesn't exist — but NOT 503
    assert_ne!(status, StatusCode::SERVICE_UNAVAILABLE);
}

/// Verifies: SYS-0024 §3 — GET /operation-timings accessible during init
#[tokio::test]
async fn test_operation_timings_allowed_during_init() {
    let (api, _dir) = create_initializing_api().await;
    let (status, _json, _headers) = make_request_full(api, "GET", "/operation-timings", None).await;

    assert_eq!(status, StatusCode::OK);
}

// ---------------------------------------------------------------------------
// Verify gate lifts after init completes (SYS-0024, SYS-0019 §4)
// ---------------------------------------------------------------------------

/// Verifies: SYS-0024 — Mutation endpoints work once init completes
#[tokio::test]
async fn test_publish_works_after_init_completes() {
    let (api, _dir) = create_ready_api().await;
    let body = json!({"queue": "test-q", "priority": 1, "payload": "{\"x\":1}"});
    let (status, _json, _headers) = make_request_full(api, "POST", "/publish", Some(body)).await;

    // Should succeed (200) not 503
    assert_ne!(status, StatusCode::SERVICE_UNAVAILABLE);
    assert_eq!(status, StatusCode::OK);
}

/// Verifies: SYS-0024 — Create queue works once init completes
#[tokio::test]
async fn test_create_queue_works_after_init_completes() {
    let (api, _dir) = create_ready_api().await;
    let body = json!({"name": "test-q", "ordering": "MaxFirst"});
    let (status, _json, _headers) =
        make_request_full(api, "POST", "/create-queue", Some(body)).await;

    assert_ne!(status, StatusCode::SERVICE_UNAVAILABLE);
}

/// Verifies: SYS-0024 — Gate lifts dynamically when flag transitions
#[tokio::test]
async fn test_gate_lifts_when_flag_cleared() {
    let temp_dir = TempDir::new().expect("Failed to create temp directory");
    let storage =
        Storage::new(temp_dir.path().to_str().unwrap()).expect("Failed to create test storage");
    let flag = Arc::new(AtomicBool::new(true));
    let api = ApiServer::new(Arc::new(storage)).with_initializing(flag.clone());

    // While init: publish is blocked
    let body = json!({"queue": "test-q", "priority": 1, "payload": "{}"});
    let (status, _, _) =
        make_request_full(api.clone(), "POST", "/publish", Some(body.clone())).await;
    assert_eq!(status, StatusCode::SERVICE_UNAVAILABLE);

    // Clear the flag
    flag.store(false, Ordering::SeqCst);

    // Now publish should succeed
    let (status, _, _) = make_request_full(api, "POST", "/publish", Some(body)).await;
    assert_ne!(status, StatusCode::SERVICE_UNAVAILABLE);
    assert_eq!(status, StatusCode::OK);
}
