//! Real Backend Integration Tests for API Gateway
//!
//! Tests that verify the API Gateway correctly proxies requests to real backend services:
//! - Trading Service (port 50052)
//! - Backtesting Service (port 50053)
//! - ML Training Service (port 50054)
//!
//! All tests use REAL gRPC communication with REAL services (no mocks).

#[path = "common/mod.rs"]
mod common;

use anyhow::Result;
use common::{cleanup_redis, generate_test_token, wait_for_redis};
use std::time::Duration;
use tonic::transport::Channel;
use tonic::Request;

const REDIS_URL: &str = "redis://localhost:6379";
const API_GATEWAY_URL: &str = "http://localhost:50051";
const TRADING_SERVICE_URL: &str = "http://localhost:50052";
const BACKTESTING_SERVICE_URL: &str = "http://localhost:50053";
const ML_TRAINING_SERVICE_URL: &str = "http://localhost:50054";

// Import proto definitions - we'll use tli's generated proto that includes all service definitions
// API Gateway tests need to use tli's proto definitions since they're testing the client-facing interface
use fxt::proto::health::{health_client::HealthClient, HealthCheckRequest};

// We use the standard health client for all service health checks

// ML Training proto from api
use api::ml_training::{
    ml_training_service_client::MlTrainingServiceClient, HealthCheckRequest as MlHealthRequest,
};

/// Helper to wait for a service to be ready
async fn wait_for_service_ready(url: &str, service_name: &str) -> Result<()> {
    let max_attempts = 30;
    let retry_delay = Duration::from_millis(500);

    for attempt in 1..=max_attempts {
        match tokio::net::TcpStream::connect(url.trim_start_matches("http://")).await {
            Ok(_) => {
                println!("✓ {} is ready (attempt {})", service_name, attempt);
                return Ok(());
            },
            Err(_) if attempt < max_attempts => {
                tokio::time::sleep(retry_delay).await;
            },
            Err(e) => {
                return Err(anyhow::anyhow!(
                    "{} not ready after {} attempts: {}",
                    service_name,
                    max_attempts,
                    e
                ));
            },
        }
    }

    unreachable!()
}

/// Setup test environment (Redis, wait for services)
async fn setup_test_environment() -> Result<()> {
    // Wait for Redis
    wait_for_redis(REDIS_URL, 50).await?;
    cleanup_redis(REDIS_URL).await?;

    // Wait for all backend services
    wait_for_service_ready("localhost:50051", "API Gateway").await?;
    wait_for_service_ready("localhost:50052", "Trading Service").await?;
    wait_for_service_ready("localhost:50053", "Backtesting Service").await?;
    wait_for_service_ready("localhost:50054", "ML Training Service").await?;

    println!("✓ All services are ready for testing");
    Ok(())
}

// ============================================================================
// TRADING SERVICE INTEGRATION TESTS
// ============================================================================

#[tokio::test]
async fn test_trading_service_direct_connection() -> Result<()> {
    println!("\n=== Test: Trading Service Direct Connection (No Proxy) ===");

    setup_test_environment().await?;

    // Connect directly to Trading Service (bypass API Gateway)
    let channel = Channel::from_static(TRADING_SERVICE_URL)
        .connect()
        .await
        .map_err(|e| anyhow::anyhow!("Failed to connect to Trading Service: {}", e))?;

    let mut client = HealthClient::new(channel);

    // Call health check
    let request = Request::new(HealthCheckRequest {
        service: "trading".to_string(),
    });
    let response = client
        .check(request)
        .await
        .map_err(|e| anyhow::anyhow!("Trading Service health check failed: {}", e))?;

    let health = response.into_inner();
    use fxt::proto::health::ServingStatus;
    let status_str = match ServingStatus::try_from(health.status) {
        Ok(ServingStatus::Serving) => "serving",
        Ok(ServingStatus::NotServing) => "not_serving",
        Ok(ServingStatus::Unknown) | Ok(ServingStatus::ServiceUnknown) | Err(_) => "unknown",
    };
    println!("✓ Trading Service health: {}", status_str);
    assert_eq!(
        health.status, ServingStatus::Serving as i32,
        "Trading Service should be serving"
    );

    Ok(())
}

#[tokio::test]
async fn test_trading_service_via_api_proxy() -> Result<()> {
    println!("\n=== Test: Trading Service via API Gateway Proxy ===");

    setup_test_environment().await?;

    // Generate valid JWT token
    let (token, _jti) = generate_test_token(
        "test_user",
        vec!["trader".to_string()],
        vec!["api.access".to_string(), "trading.submit".to_string()],
        3600,
    )?;

    // Connect to API Gateway (which proxies to Trading Service)
    let channel = Channel::from_static(API_GATEWAY_URL)
        .connect()
        .await
        .map_err(|e| anyhow::anyhow!("Failed to connect to API Gateway: {}", e))?;

    let mut client = HealthClient::new(channel);

    // Call health check through API Gateway proxy
    let mut request = Request::new(HealthCheckRequest {
        service: "trading".to_string(),
    });
    request.metadata_mut().insert(
        "authorization",
        format!("Bearer {}", token).parse().expect("INVARIANT: Valid parse input"),
    );

    let start = std::time::Instant::now();
    let response = client
        .check(request)
        .await
        .map_err(|e| anyhow::anyhow!("Trading Service health check via proxy failed: {}", e))?;
    let elapsed = start.elapsed();

    let health = response.into_inner();
    use fxt::proto::health::ServingStatus;
    let status_str = match ServingStatus::try_from(health.status) {
        Ok(ServingStatus::Serving) => "serving",
        Ok(ServingStatus::NotServing) => "not_serving",
        Ok(ServingStatus::Unknown) | Ok(ServingStatus::ServiceUnknown) | Err(_) => "unknown",
    };
    println!("✓ Trading Service health via proxy: {}", status_str);
    println!("  Proxy latency: {:?} (target: <1ms)", elapsed);

    assert_eq!(
        health.status, ServingStatus::Serving as i32,
        "Trading Service should be serving"
    );
    assert!(
        elapsed < Duration::from_millis(50),
        "Proxy latency should be <50ms, got {:?}",
        elapsed
    );

    Ok(())
}

#[tokio::test]
async fn test_trading_service_proxy_requires_auth() -> Result<()> {
    println!("\n=== Test: Trading Service Proxy Requires Authentication ===");

    setup_test_environment().await?;

    // Connect to API Gateway WITHOUT auth token
    let channel = Channel::from_static(API_GATEWAY_URL)
        .connect()
        .await
        .map_err(|e| anyhow::anyhow!("Failed to connect to API Gateway: {}", e))?;

    let mut client = HealthClient::new(channel);

    // Call health check without auth header
    let request = Request::new(HealthCheckRequest {
        service: "trading".to_string(),
    });
    let result = client.check(request).await;

    // Should fail with UNAUTHENTICATED
    assert!(
        result.is_err(),
        "Request without auth should be rejected by API Gateway"
    );

    let error = result.unwrap_err();
    println!("✓ Correctly rejected: {:?}", error.code());
    assert_eq!(
        error.code(),
        tonic::Code::Unauthenticated,
        "Should return UNAUTHENTICATED"
    );

    Ok(())
}

// ============================================================================
// BACKTESTING SERVICE INTEGRATION TESTS
// ============================================================================

#[tokio::test]
async fn test_backtesting_service_direct_connection() -> Result<()> {
    println!("\n=== Test: Backtesting Service Direct Connection (No Proxy) ===");

    setup_test_environment().await?;

    // Connect directly to Backtesting Service (bypass API Gateway)
    let channel = Channel::from_static(BACKTESTING_SERVICE_URL)
        .connect()
        .await
        .map_err(|e| anyhow::anyhow!("Failed to connect to Backtesting Service: {}", e))?;

    let mut client = HealthClient::new(channel);

    // Call health check
    let request = Request::new(HealthCheckRequest {
        service: "backtesting".to_string(),
    });
    let response = client
        .check(request)
        .await
        .map_err(|e| anyhow::anyhow!("Backtesting Service health check failed: {}", e))?;

    let health = response.into_inner();
    use fxt::proto::health::ServingStatus;
    let status_str = match ServingStatus::try_from(health.status) {
        Ok(ServingStatus::Serving) => "serving",
        Ok(ServingStatus::NotServing) => "not_serving",
        Ok(ServingStatus::Unknown) | Ok(ServingStatus::ServiceUnknown) | Err(_) => "unknown",
    };
    println!("✓ Backtesting Service health: {}", status_str);
    assert_eq!(
        health.status, ServingStatus::Serving as i32,
        "Backtesting Service should be serving"
    );

    Ok(())
}

#[tokio::test]
async fn test_backtesting_service_via_api_proxy() -> Result<()> {
    println!("\n=== Test: Backtesting Service via API Gateway Proxy ===");

    setup_test_environment().await?;

    // Generate valid JWT token
    let (token, _jti) = generate_test_token(
        "test_user",
        vec!["trader".to_string()],
        vec!["api.access".to_string(), "backtesting.run".to_string()],
        3600,
    )?;

    // Connect to API Gateway (which proxies to Backtesting Service)
    let channel = Channel::from_static(API_GATEWAY_URL)
        .connect()
        .await
        .map_err(|e| anyhow::anyhow!("Failed to connect to API Gateway: {}", e))?;

    let mut client = HealthClient::new(channel);

    // Call health check through API Gateway proxy
    let mut request = Request::new(HealthCheckRequest {
        service: "backtesting".to_string(),
    });
    request.metadata_mut().insert(
        "authorization",
        format!("Bearer {}", token).parse().expect("INVARIANT: Valid parse input"),
    );

    let start = std::time::Instant::now();
    let response = client
        .check(request)
        .await
        .map_err(|e| anyhow::anyhow!("Backtesting Service health check via proxy failed: {}", e))?;
    let elapsed = start.elapsed();

    let health = response.into_inner();
    use fxt::proto::health::ServingStatus;
    let status_str = match ServingStatus::try_from(health.status) {
        Ok(ServingStatus::Serving) => "serving",
        Ok(ServingStatus::NotServing) => "not_serving",
        Ok(ServingStatus::Unknown) | Ok(ServingStatus::ServiceUnknown) | Err(_) => "unknown",
    };
    println!("✓ Backtesting Service health via proxy: {}", status_str);
    println!("  Proxy latency: {:?} (target: <1ms)", elapsed);

    assert_eq!(
        health.status, ServingStatus::Serving as i32,
        "Backtesting Service should be serving"
    );
    assert!(
        elapsed < Duration::from_millis(50),
        "Proxy latency should be <50ms, got {:?}",
        elapsed
    );

    Ok(())
}

#[tokio::test]
async fn test_backtesting_service_proxy_requires_auth() -> Result<()> {
    println!("\n=== Test: Backtesting Service Proxy Requires Authentication ===");

    setup_test_environment().await?;

    // Connect to API Gateway WITHOUT auth token
    let channel = Channel::from_static(API_GATEWAY_URL)
        .connect()
        .await
        .map_err(|e| anyhow::anyhow!("Failed to connect to API Gateway: {}", e))?;

    let mut client = HealthClient::new(channel);

    // Call health check without auth header
    let request = Request::new(HealthCheckRequest {
        service: "backtesting".to_string(),
    });
    let result = client.check(request).await;

    // Should fail with UNAUTHENTICATED
    assert!(
        result.is_err(),
        "Request without auth should be rejected by API Gateway"
    );

    let error = result.unwrap_err();
    println!("✓ Correctly rejected: {:?}", error.code());
    assert_eq!(
        error.code(),
        tonic::Code::Unauthenticated,
        "Should return UNAUTHENTICATED"
    );

    Ok(())
}

// ============================================================================
// ML TRAINING SERVICE INTEGRATION TESTS
// ============================================================================

#[tokio::test]
async fn test_ml_training_service_direct_connection() -> Result<()> {
    println!("\n=== Test: ML Training Service Direct Connection (No Proxy) ===");

    setup_test_environment().await?;

    // Connect directly to ML Training Service (bypass API Gateway)
    let channel = Channel::from_static(ML_TRAINING_SERVICE_URL)
        .connect()
        .await
        .map_err(|e| anyhow::anyhow!("Failed to connect to ML Training Service: {}", e))?;

    let mut client = MlTrainingServiceClient::new(channel);

    // Call health check
    let request = Request::new(MlHealthRequest {});
    let response = client
        .health_check(request)
        .await
        .map_err(|e| anyhow::anyhow!("ML Training Service health check failed: {}", e))?;

    let health = response.into_inner();
    println!("✓ ML Training Service health: {}", if health.healthy { "healthy" } else { "unhealthy" });
    assert!(
        health.healthy,
        "ML Training Service should be healthy: {}",
        health.message
    );

    Ok(())
}

#[tokio::test]
async fn test_ml_training_service_via_api_proxy() -> Result<()> {
    println!("\n=== Test: ML Training Service via API Gateway Proxy ===");

    setup_test_environment().await?;

    // Generate valid JWT token
    let (token, _jti) = generate_test_token(
        "test_user",
        vec!["admin".to_string()],
        vec!["api.access".to_string(), "ml.train".to_string()],
        3600,
    )?;

    // Connect to API Gateway (which proxies to ML Training Service)
    let channel = Channel::from_static(API_GATEWAY_URL)
        .connect()
        .await
        .map_err(|e| anyhow::anyhow!("Failed to connect to API Gateway: {}", e))?;

    let mut client = MlTrainingServiceClient::new(channel);

    // Call health check through API Gateway proxy
    let mut request = Request::new(MlHealthRequest {});
    request.metadata_mut().insert(
        "authorization",
        format!("Bearer {}", token).parse().expect("INVARIANT: Valid parse input"),
    );

    let start = std::time::Instant::now();
    let response = client
        .health_check(request)
        .await
        .map_err(|e| anyhow::anyhow!("ML Training Service health check via proxy failed: {}", e))?;
    let elapsed = start.elapsed();

    let health = response.into_inner();
    println!("✓ ML Training Service health via proxy: {}", if health.healthy { "healthy" } else { "unhealthy" });
    println!("  Proxy latency: {:?} (target: <1ms)", elapsed);

    assert!(
        health.healthy,
        "ML Training Service should be healthy: {}",
        health.message
    );
    assert!(
        elapsed < Duration::from_millis(50),
        "Proxy latency should be <50ms, got {:?}",
        elapsed
    );

    Ok(())
}

#[tokio::test]
async fn test_ml_training_service_proxy_requires_auth() -> Result<()> {
    println!("\n=== Test: ML Training Service Proxy Requires Authentication ===");

    setup_test_environment().await?;

    // Connect to API Gateway WITHOUT auth token
    let channel = Channel::from_static(API_GATEWAY_URL)
        .connect()
        .await
        .map_err(|e| anyhow::anyhow!("Failed to connect to API Gateway: {}", e))?;

    let mut client = MlTrainingServiceClient::new(channel);

    // Call health check without auth header
    let request = Request::new(MlHealthRequest {});
    let result = client.health_check(request).await;

    // Should fail with UNAUTHENTICATED
    assert!(
        result.is_err(),
        "Request without auth should be rejected by API Gateway"
    );

    let error = result.unwrap_err();
    println!("✓ Correctly rejected: {:?}", error.code());
    assert_eq!(
        error.code(),
        tonic::Code::Unauthenticated,
        "Should return UNAUTHENTICATED"
    );

    Ok(())
}

// ============================================================================
// MULTI-SERVICE INTEGRATION TESTS
// ============================================================================

#[tokio::test]
async fn test_api_routes_to_all_backend_services() -> Result<()> {
    println!("\n=== Test: API Gateway Routes to All Backend Services ===");

    setup_test_environment().await?;

    // Generate valid JWT token
    let (token, _jti) = generate_test_token(
        "test_user",
        vec!["admin".to_string(), "trader".to_string()],
        vec![
            "api.access".to_string(),
            "trading.submit".to_string(),
            "backtesting.run".to_string(),
            "ml.train".to_string(),
        ],
        3600,
    )?;

    // Connect to API Gateway once
    let channel = Channel::from_static(API_GATEWAY_URL)
        .connect()
        .await
        .map_err(|e| anyhow::anyhow!("Failed to connect to API Gateway: {}", e))?;

    // Test Trading Service routing
    {
        let mut client = HealthClient::new(channel.clone());
        let mut request = Request::new(HealthCheckRequest {
            service: "trading".to_string(),
        });
        request.metadata_mut().insert(
            "authorization",
            format!("Bearer {}", token).parse().expect("INVARIANT: Valid parse input"),
        );

        let response = client.check(request).await?;
        use fxt::proto::health::ServingStatus;
        let health = response.into_inner();
        let status_str = match ServingStatus::try_from(health.status) {
            Ok(ServingStatus::Serving) => "serving",
            Ok(ServingStatus::NotServing) => "not_serving",
            Ok(ServingStatus::Unknown) | Ok(ServingStatus::ServiceUnknown) | Err(_) => "unknown",
        };
        println!("  ✓ Trading Service: {}", status_str);
    }

    // Test Backtesting Service routing
    {
        let mut client = HealthClient::new(channel.clone());
        let mut request = Request::new(HealthCheckRequest {
            service: "backtesting".to_string(),
        });
        request.metadata_mut().insert(
            "authorization",
            format!("Bearer {}", token).parse().expect("INVARIANT: Valid parse input"),
        );

        let response = client.check(request).await?;
        use fxt::proto::health::ServingStatus;
        let health = response.into_inner();
        let status_str = match ServingStatus::try_from(health.status) {
            Ok(ServingStatus::Serving) => "serving",
            Ok(ServingStatus::NotServing) => "not_serving",
            Ok(ServingStatus::Unknown) | Ok(ServingStatus::ServiceUnknown) | Err(_) => "unknown",
        };
        println!("  ✓ Backtesting Service: {}", status_str);
    }

    // Test ML Training Service routing
    {
        let mut client = MlTrainingServiceClient::new(channel.clone());
        let mut request = Request::new(MlHealthRequest {});
        request.metadata_mut().insert(
            "authorization",
            format!("Bearer {}", token).parse().expect("INVARIANT: Valid parse input"),
        );

        let response = client.health_check(request).await?;
        let health = response.into_inner();
        println!("  ✓ ML Training Service: {}", if health.healthy { "healthy" } else { "unhealthy" });
    }

    println!("✓ API Gateway successfully routes to all 3 backend services");

    Ok(())
}

#[tokio::test]
async fn test_api_proxy_latency_across_services() -> Result<()> {
    println!("\n=== Test: API Gateway Proxy Latency Across All Services ===");

    setup_test_environment().await?;

    // Generate valid JWT token
    let (token, _jti) = generate_test_token(
        "test_user",
        vec!["admin".to_string(), "trader".to_string()],
        vec![
            "api.access".to_string(),
            "trading.submit".to_string(),
            "backtesting.run".to_string(),
            "ml.train".to_string(),
        ],
        3600,
    )?;

    let channel = Channel::from_static(API_GATEWAY_URL).connect().await?;

    let mut latencies = Vec::new();

    // Measure Trading Service latency (10 samples)
    for _ in 0..10 {
        let mut client = HealthClient::new(channel.clone());
        let mut request = Request::new(HealthCheckRequest {
            service: "trading".to_string(),
        });
        request.metadata_mut().insert(
            "authorization",
            format!("Bearer {}", token).parse().expect("INVARIANT: Valid parse input"),
        );

        let start = std::time::Instant::now();
        let _ = client.check(request).await?;
        latencies.push(start.elapsed());
    }

    // Calculate P50, P95, P99
    latencies.sort();
    let p50 = latencies[latencies.len() / 2];
    let p95 = latencies[latencies.len() * 95 / 100];
    let p99 = latencies[latencies.len() * 99 / 100];

    println!("\n  Proxy Latency Statistics:");
    println!("  ├─ P50: {:?}", p50);
    println!("  ├─ P95: {:?}", p95);
    println!("  └─ P99: {:?}", p99);

    assert!(
        p99 < Duration::from_millis(50),
        "P99 latency should be <50ms, got {:?}",
        p99
    );
    println!("✓ Proxy latency within acceptable bounds");

    Ok(())
}
