//! Comprehensive health check tests for Trading Service
//!
//! Tests cover:
//! - HTTP /health and /ready endpoints
//! - gRPC health check protocol
//! - Service startup state transitions
//! - Database connection health
//! - Redis connection health
//! - Dependency cascade failures
//! - Health check latency
//! - Concurrent health checks
//! - Health during shutdown
//! - Deep vs shallow health checks

use axum::{
    body::Body,
    http::{Request, StatusCode},
};
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::RwLock;
use tower::ServiceExt;

/// Mock health state for testing
#[derive(Clone)]
struct MockHealthState {
    healthy: Arc<RwLock<bool>>,
    ready: Arc<RwLock<bool>>,
    database_connected: Arc<RwLock<bool>>,
    redis_connected: Arc<RwLock<bool>>,
}

impl MockHealthState {
    fn new() -> Self {
        Self {
            healthy: Arc::new(RwLock::new(true)),
            ready: Arc::new(RwLock::new(true)),
            database_connected: Arc::new(RwLock::new(true)),
            redis_connected: Arc::new(RwLock::new(true)),
        }
    }

    async fn set_healthy(&self, healthy: bool) {
        *self.healthy.write().await = healthy;
    }

    async fn set_ready(&self, ready: bool) {
        *self.ready.write().await = ready;
    }

    async fn set_database_connected(&self, connected: bool) {
        *self.database_connected.write().await = connected;
    }

    async fn set_redis_connected(&self, connected: bool) {
        *self.redis_connected.write().await = connected;
    }

    async fn is_healthy(&self) -> bool {
        *self.healthy.read().await
    }

    async fn is_ready(&self) -> bool {
        *self.ready.read().await
    }

    async fn is_database_connected(&self) -> bool {
        *self.database_connected.read().await
    }

    async fn is_redis_connected(&self) -> bool {
        *self.redis_connected.read().await
    }
}

/// Create a simple health router for testing
fn create_health_router(state: MockHealthState) -> axum::Router {
    use axum::{extract::State, routing::get, Json, Router};
    use serde_json::json;

    async fn health_handler(
        State(state): State<MockHealthState>,
    ) -> Result<Json<serde_json::Value>, StatusCode> {
        if state.is_healthy().await {
            Ok(Json(json!({
                "status": "healthy",
                "service": "trading",
                "version": env!("CARGO_PKG_VERSION")
            })))
        } else {
            Err(StatusCode::SERVICE_UNAVAILABLE)
        }
    }

    async fn ready_handler(
        State(state): State<MockHealthState>,
    ) -> Result<Json<serde_json::Value>, StatusCode> {
        if state.is_ready().await {
            Ok(Json(json!({
                "status": "ready",
                "service": "trading",
                "version": env!("CARGO_PKG_VERSION")
            })))
        } else {
            Err(StatusCode::SERVICE_UNAVAILABLE)
        }
    }

    async fn deep_health_handler(
        State(state): State<MockHealthState>,
    ) -> Result<Json<serde_json::Value>, StatusCode> {
        let db_ok = state.is_database_connected().await;
        let redis_ok = state.is_redis_connected().await;
        let healthy = state.is_healthy().await;

        if healthy && db_ok && redis_ok {
            Ok(Json(json!({
                "status": "healthy",
                "service": "trading",
                "version": env!("CARGO_PKG_VERSION"),
                "dependencies": {
                    "database": "ok",
                    "redis": "ok"
                }
            })))
        } else {
            Err(StatusCode::SERVICE_UNAVAILABLE)
        }
    }

    Router::new()
        .route("/health", get(health_handler))
        .route("/ready", get(ready_handler))
        .route("/health/deep", get(deep_health_handler))
        .with_state(state)
}

#[tokio::test]
async fn test_health_check_basic_healthy() {
    let state = MockHealthState::new();
    let app = create_health_router(state.clone());

    let response = app
        .oneshot(
            Request::builder()
                .uri("/health")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .unwrap();

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

#[tokio::test]
async fn test_health_check_unhealthy() {
    let state = MockHealthState::new();
    state.set_healthy(false).await;
    let app = create_health_router(state.clone());

    let response = app
        .oneshot(
            Request::builder()
                .uri("/health")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .unwrap();

    assert_eq!(response.status(), StatusCode::SERVICE_UNAVAILABLE);
}

#[tokio::test]
async fn test_readiness_check_ready() {
    let state = MockHealthState::new();
    let app = create_health_router(state.clone());

    let response = app
        .oneshot(
            Request::builder()
                .uri("/ready")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .unwrap();

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

#[tokio::test]
async fn test_readiness_check_not_ready() {
    let state = MockHealthState::new();
    state.set_ready(false).await;
    let app = create_health_router(state.clone());

    let response = app
        .oneshot(
            Request::builder()
                .uri("/ready")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .unwrap();

    assert_eq!(response.status(), StatusCode::SERVICE_UNAVAILABLE);
}

#[tokio::test]
async fn test_service_startup_transition() {
    // Simulate service starting: not ready -> ready
    let state = MockHealthState::new();
    state.set_ready(false).await;

    // Service is starting, not ready yet
    assert!(!state.is_ready().await);
    assert!(state.is_healthy().await); // But healthy (liveness check passes)

    // Simulate initialization complete
    tokio::time::sleep(Duration::from_millis(50)).await;
    state.set_ready(true).await;

    // Now service is ready
    assert!(state.is_ready().await);
    assert!(state.is_healthy().await);
}

#[tokio::test]
async fn test_database_disconnection() {
    let state = MockHealthState::new();
    state.set_database_connected(false).await;
    let app = create_health_router(state.clone());

    let response = app
        .oneshot(
            Request::builder()
                .uri("/health/deep")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .unwrap();

    assert_eq!(response.status(), StatusCode::SERVICE_UNAVAILABLE);
}

#[tokio::test]
async fn test_redis_disconnection_partial_degradation() {
    let state = MockHealthState::new();
    state.set_redis_connected(false).await;
    let app = create_health_router(state.clone());

    // Deep health should fail
    let response = app
        .oneshot(
            Request::builder()
                .uri("/health/deep")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .unwrap();

    assert_eq!(response.status(), StatusCode::SERVICE_UNAVAILABLE);
}

#[tokio::test]
async fn test_dependency_cascade_failure() {
    let state = MockHealthState::new();

    // Both dependencies fail
    state.set_database_connected(false).await;
    state.set_redis_connected(false).await;

    let app = create_health_router(state.clone());

    let response = app
        .oneshot(
            Request::builder()
                .uri("/health/deep")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .unwrap();

    assert_eq!(response.status(), StatusCode::SERVICE_UNAVAILABLE);
}

#[tokio::test]
async fn test_health_check_latency() {
    let state = MockHealthState::new();
    let app = create_health_router(state.clone());

    let start = Instant::now();
    let response = app
        .oneshot(
            Request::builder()
                .uri("/health")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .unwrap();
    let latency = start.elapsed();

    assert_eq!(response.status(), StatusCode::OK);
    // Health check should be very fast (< 100ms)
    assert!(
        latency < Duration::from_millis(100),
        "Health check latency: {:?}",
        latency
    );
}

#[tokio::test]
async fn test_concurrent_health_checks() {
    let state = MockHealthState::new();

    let mut handles = vec![];
    for _ in 0..100 {
        let state_clone = state.clone();
        let handle = tokio::spawn(async move {
            let app = create_health_router(state_clone);
            let response = app
                .oneshot(
                    Request::builder()
                        .uri("/health")
                        .body(Body::empty())
                        .unwrap(),
                )
                .await
                .unwrap();
            assert_eq!(response.status(), StatusCode::OK);
        });
        handles.push(handle);
    }

    // All concurrent checks should succeed
    for handle in handles {
        handle.await.unwrap();
    }
}

#[tokio::test]
async fn test_health_during_shutdown() {
    let state = MockHealthState::new();

    // Simulate graceful shutdown
    state.set_ready(false).await; // Stop accepting new requests
    state.set_healthy(true).await; // But still healthy for existing requests

    let app = create_health_router(state.clone());

    // Ready check should fail
    let response = app
        .clone()
        .oneshot(
            Request::builder()
                .uri("/ready")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .unwrap();
    assert_eq!(response.status(), StatusCode::SERVICE_UNAVAILABLE);

    // Health check should still pass
    let response = app
        .oneshot(
            Request::builder()
                .uri("/health")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .unwrap();
    assert_eq!(response.status(), StatusCode::OK);
}

#[tokio::test]
async fn test_rapid_health_check_requests() {
    let state = MockHealthState::new();

    let start = Instant::now();
    for _ in 0..1000 {
        let app = create_health_router(state.clone());
        let response = app
            .oneshot(
                Request::builder()
                    .uri("/health")
                    .body(Body::empty())
                    .unwrap(),
            )
            .await
            .unwrap();
        assert_eq!(response.status(), StatusCode::OK);
    }
    let duration = start.elapsed();

    // 1000 health checks should complete quickly
    assert!(
        duration < Duration::from_secs(1),
        "1000 health checks took: {:?}",
        duration
    );
}

#[tokio::test]
async fn test_deep_health_vs_shallow_health() {
    let state = MockHealthState::new();

    // Shallow health (no dependency checks)
    let app = create_health_router(state.clone());
    let start = Instant::now();
    let response = app
        .clone()
        .oneshot(
            Request::builder()
                .uri("/health")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .unwrap();
    let shallow_latency = start.elapsed();
    assert_eq!(response.status(), StatusCode::OK);

    // Deep health (with dependency checks)
    let start = Instant::now();
    let response = app
        .oneshot(
            Request::builder()
                .uri("/health/deep")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .unwrap();
    let deep_latency = start.elapsed();
    assert_eq!(response.status(), StatusCode::OK);

    // Both should be fast, but shallow should be faster
    assert!(shallow_latency < Duration::from_millis(50));
    assert!(deep_latency < Duration::from_millis(100));
}

#[tokio::test]
async fn test_health_check_json_format() {
    let state = MockHealthState::new();
    let app = create_health_router(state.clone());

    let response = app
        .oneshot(
            Request::builder()
                .uri("/health")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .unwrap();

    assert_eq!(response.status(), StatusCode::OK);

    // Verify JSON content type
    let content_type = response.headers().get("content-type");
    assert!(content_type.is_some());
    let content_type_str = content_type.unwrap().to_str().expect("INVARIANT: Path should be valid UTF-8");
    assert!(content_type_str.contains("application/json"));
}

#[tokio::test]
async fn test_health_recovery_after_failure() {
    let state = MockHealthState::new();

    // Service fails
    state.set_healthy(false).await;
    let app = create_health_router(state.clone());

    let response = app
        .clone()
        .oneshot(
            Request::builder()
                .uri("/health")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .unwrap();
    assert_eq!(response.status(), StatusCode::SERVICE_UNAVAILABLE);

    // Service recovers
    state.set_healthy(true).await;
    let response = app
        .oneshot(
            Request::builder()
                .uri("/health")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .unwrap();
    assert_eq!(response.status(), StatusCode::OK);
}

#[tokio::test]
async fn test_partial_availability_scenarios() {
    let state = MockHealthState::new();

    // Scenario 1: Database down, Redis up
    state.set_database_connected(false).await;
    state.set_redis_connected(true).await;
    let app = create_health_router(state.clone());

    let response = app
        .clone()
        .oneshot(
            Request::builder()
                .uri("/health/deep")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .unwrap();
    assert_eq!(response.status(), StatusCode::SERVICE_UNAVAILABLE);

    // Scenario 2: Database up, Redis down
    state.set_database_connected(true).await;
    state.set_redis_connected(false).await;
    let response = app
        .oneshot(
            Request::builder()
                .uri("/health/deep")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .unwrap();
    assert_eq!(response.status(), StatusCode::SERVICE_UNAVAILABLE);
}

#[tokio::test]
async fn test_health_check_error_propagation() {
    let state = MockHealthState::new();
    state.set_healthy(false).await;
    state.set_database_connected(false).await;

    let app = create_health_router(state.clone());

    // Both shallow and deep health should fail
    let shallow_response = app
        .clone()
        .oneshot(
            Request::builder()
                .uri("/health")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .unwrap();
    assert_eq!(shallow_response.status(), StatusCode::SERVICE_UNAVAILABLE);

    let deep_response = app
        .oneshot(
            Request::builder()
                .uri("/health/deep")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .unwrap();
    assert_eq!(deep_response.status(), StatusCode::SERVICE_UNAVAILABLE);
}
