#![allow(unexpected_cfgs)]
#![cfg(feature = "__backtesting_integration")]
//! Comprehensive gRPC Error Handling Tests for Backtesting Service
//!
//! This test suite validates all gRPC error codes and edge cases for the Backtesting Service,
//! focusing on backtest job management, data validation, and resource constraints.
//!
//! Coverage areas:
//! - InvalidArgument: Invalid date ranges, missing symbols, bad parameters
//! - NotFound: Non-existent backtest jobs
//! - FailedPrecondition: Invalid job state transitions
//! - ResourceExhausted: Too many concurrent jobs
//! - Internal: Data loading failures, computation errors
//! - DeadlineExceeded: Long-running backtests
//! - Aborted: Job cancellation handling
//!
//! Total: 12 comprehensive error scenario tests

use anyhow::Result;
use std::time::Duration;
use tonic::{Code, Request};

// Import TLI proto definitions (Backtesting Service interface)
use backtesting_service::foxhunt::tli::{
    backtesting_service_client::BacktestingServiceClient, GetBacktestResultsRequest,
    GetBacktestStatusRequest, StartBacktestRequest, StopBacktestRequest,
};

// ============================================================================
// HELPER FUNCTIONS
// ============================================================================

/// Create authenticated Backtesting Service client
///
/// Returns a client with an interceptor that adds JWT authentication headers to all requests.
///
/// # Implementation Note
/// We create the client and return it directly. The caller receives the intercepted client
/// but the specific closure type is opaque. Each call site must let Rust infer the type
/// or use it immediately without storing in a variable with an explicit type annotation.
async fn create_authenticated_client() -> Result<
    BacktestingServiceClient<
        tonic::service::interceptor::InterceptedService<
            tonic::transport::Channel,
            impl tonic::service::Interceptor + Clone,
        >,
    >,
> {
    let channel = tonic::transport::Channel::from_static("http://localhost:50053")
        .connect()
        .await?;

    // Create valid JWT token for authentication
    let token = create_valid_jwt_token()?;

    // Create interceptor closure that adds JWT auth header
    let interceptor = move |mut req: Request<()>| {
        req.metadata_mut().insert(
            "authorization",
            format!("Bearer {}", token).parse().expect("INVARIANT: Valid parse input"),
        );
        Ok(req)
    };

    // Create client with interceptor
    let client = BacktestingServiceClient::with_interceptor(channel, interceptor);

    Ok(client)
}

/// Generate valid JWT token for testing
fn create_valid_jwt_token() -> Result<String> {
    use chrono::{Duration, Utc};
    use jsonwebtoken::{encode, Algorithm, EncodingKey, Header};
    use serde::{Deserialize, Serialize};

    #[derive(Serialize, Deserialize)]
    struct Claims {
        sub: String,
        exp: usize,
        iat: usize,
        iss: String,
        aud: String,
        roles: Vec<String>,
        permissions: Vec<String>,
        jti: String,
    }

    let jwt_secret = std::env::var("JWT_SECRET")
        .unwrap_or_else(|_| "OvFLDUbIDak3CSCi5t6zKfsAp65cjTOJ85q9YE+TFY8b361DGg1gSTra2rW6mps3cWrRGQ/NXRA5uftUpMldvOaEHMMgfBs4JjVODDElREdvUFm0EttD1A==".to_string());

    let claims = Claims {
        sub: "test_user_001".to_string(),
        exp: (Utc::now() + Duration::hours(1)).timestamp() as usize,
        iat: Utc::now().timestamp() as usize,
        iss: "foxhunt-api".to_string(),
        aud: "foxhunt-backtesting".to_string(),
        roles: vec!["trader".to_string()],
        permissions: vec!["backtest:run".to_string()],
        jti: uuid::Uuid::new_v4().to_string(),
    };

    let token = encode(
        &Header::new(Algorithm::HS256),
        &claims,
        &EncodingKey::from_secret(jwt_secret.as_bytes()),
    )?;

    Ok(token)
}

// ============================================================================
// INVALID ARGUMENT TESTS (Validation Failures)
// ============================================================================

#[tokio::test]
async fn test_start_backtest_empty_symbols_returns_invalid_argument() -> Result<()> {
    println!("\n=== Test: Start Backtest - Empty Symbols (InvalidArgument) ===");

    let mut client = create_authenticated_client().await?;

    let request = Request::new(StartBacktestRequest {
        strategy_name: "test_strategy".to_string(),
        symbols: vec![],                            // Invalid: empty symbols list
        start_date_unix_nanos: 1609459200000000000, // 2021-01-01
        end_date_unix_nanos: 1640995200000000000,   // 2022-01-01
        initial_capital: 100000.0,
        parameters: std::collections::HashMap::new(),
        save_results: true,
        description: "Test backtest".to_string(),
    });

    let result = client.start_backtest(request).await;

    assert!(result.is_err(), "Expected error for empty symbols");
    let status = result.unwrap_err();
    assert_eq!(
        status.code(),
        Code::InvalidArgument,
        "Expected InvalidArgument error code"
    );
    assert!(
        status.message().contains("symbol") || status.message().contains("empty"),
        "Error message should mention symbols"
    );

    println!("  ✓ Empty symbols list correctly rejected");
    Ok(())
}

#[tokio::test]
async fn test_start_backtest_invalid_date_range_returns_invalid_argument() -> Result<()> {
    println!("\n=== Test: Start Backtest - Invalid Date Range (InvalidArgument) ===");

    let mut client = create_authenticated_client().await?;

    let request = Request::new(StartBacktestRequest {
        strategy_name: "test_strategy".to_string(),
        symbols: vec!["BTC/USD".to_string()],
        start_date_unix_nanos: 1640995200000000000, // 2022-01-01 (after end date)
        end_date_unix_nanos: 1609459200000000000,   // 2021-01-01
        initial_capital: 100000.0,
        parameters: std::collections::HashMap::new(),
        save_results: true,
        description: "Test backtest".to_string(),
    });

    let result = client.start_backtest(request).await;

    assert!(result.is_err(), "Expected error for invalid date range");
    let status = result.unwrap_err();
    assert_eq!(status.code(), Code::InvalidArgument);
    assert!(
        status.message().contains("date") || status.message().contains("range"),
        "Error message should mention date range"
    );

    println!("  ✓ Invalid date range correctly rejected");
    Ok(())
}

#[tokio::test]
async fn test_start_backtest_zero_capital_returns_invalid_argument() -> Result<()> {
    println!("\n=== Test: Start Backtest - Zero Capital (InvalidArgument) ===");

    let mut client = create_authenticated_client().await?;

    let request = Request::new(StartBacktestRequest {
        strategy_name: "test_strategy".to_string(),
        symbols: vec!["BTC/USD".to_string()],
        start_date_unix_nanos: 1609459200000000000,
        end_date_unix_nanos: 1640995200000000000,
        initial_capital: 0.0, // Invalid: zero capital
        parameters: std::collections::HashMap::new(),
        save_results: true,
        description: "Test backtest".to_string(),
    });

    let result = client.start_backtest(request).await;

    assert!(result.is_err(), "Expected error for zero capital");
    let status = result.unwrap_err();
    assert_eq!(status.code(), Code::InvalidArgument);
    assert!(
        status.message().contains("capital") || status.message().contains("zero"),
        "Error message should mention capital"
    );

    println!("  ✓ Zero capital correctly rejected");
    Ok(())
}

#[tokio::test]
async fn test_start_backtest_empty_strategy_name_returns_invalid_argument() -> Result<()> {
    println!("\n=== Test: Start Backtest - Empty Strategy Name (InvalidArgument) ===");

    let mut client = create_authenticated_client().await?;

    let request = Request::new(StartBacktestRequest {
        strategy_name: "".to_string(), // Invalid: empty strategy name
        symbols: vec!["BTC/USD".to_string()],
        start_date_unix_nanos: 1609459200000000000,
        end_date_unix_nanos: 1640995200000000000,
        initial_capital: 100000.0,
        parameters: std::collections::HashMap::new(),
        save_results: true,
        description: "Test backtest".to_string(),
    });

    let result = client.start_backtest(request).await;

    assert!(result.is_err(), "Expected error for empty strategy name");
    let status = result.unwrap_err();
    assert_eq!(status.code(), Code::InvalidArgument);

    println!("  ✓ Empty strategy name correctly rejected");
    Ok(())
}

// ============================================================================
// NOT FOUND TESTS (Non-existent Resources)
// ============================================================================

#[tokio::test]
async fn test_get_backtest_status_nonexistent_job_returns_not_found() -> Result<()> {
    println!("\n=== Test: Get Backtest Status - Non-existent Job (NotFound) ===");

    let mut client = create_authenticated_client().await?;

    let request = Request::new(GetBacktestStatusRequest {
        backtest_id: "nonexistent_job_999999".to_string(),
    });

    let result = client.get_backtest_status(request).await;

    assert!(result.is_err(), "Expected error for non-existent job");
    let status = result.unwrap_err();
    assert_eq!(
        status.code(),
        Code::NotFound,
        "Expected NotFound error code"
    );
    assert!(
        status.message().contains("not found") || status.message().contains("exist"),
        "Error message should mention not found"
    );

    println!("  ✓ Non-existent job correctly returns NotFound");
    Ok(())
}

#[tokio::test]
async fn test_get_backtest_results_nonexistent_job_returns_not_found() -> Result<()> {
    println!("\n=== Test: Get Backtest Results - Non-existent Job (NotFound) ===");

    let mut client = create_authenticated_client().await?;

    let request = Request::new(GetBacktestResultsRequest {
        backtest_id: "nonexistent_job_888888".to_string(),
        include_trades: true,
        include_metrics: true,
    });

    let result = client.get_backtest_results(request).await;

    assert!(result.is_err(), "Expected error for non-existent job");
    let status = result.unwrap_err();
    assert_eq!(status.code(), Code::NotFound);

    println!("  ✓ Non-existent results correctly returns NotFound");
    Ok(())
}

#[tokio::test]
async fn test_stop_backtest_nonexistent_job_returns_not_found() -> Result<()> {
    println!("\n=== Test: Stop Backtest - Non-existent Job (NotFound) ===");

    let mut client = create_authenticated_client().await?;

    let request = Request::new(StopBacktestRequest {
        backtest_id: "nonexistent_job_777777".to_string(),
        save_partial_results: false,
    });

    let result = client.stop_backtest(request).await;

    assert!(result.is_err(), "Expected error for non-existent job");
    let status = result.unwrap_err();
    assert_eq!(status.code(), Code::NotFound);

    println!("  ✓ Stop non-existent job correctly returns NotFound");
    Ok(())
}

// ============================================================================
// FAILED PRECONDITION TESTS (Invalid State Transitions)
// ============================================================================

#[tokio::test]
async fn test_get_results_incomplete_backtest_returns_failed_precondition() -> Result<()> {
    println!("\n=== Test: Get Results - Incomplete Backtest (FailedPrecondition) ===");

    let mut client = create_authenticated_client().await?;

    // Start a backtest
    let start_request = Request::new(StartBacktestRequest {
        strategy_name: "test_strategy".to_string(),
        symbols: vec!["BTC/USD".to_string()],
        start_date_unix_nanos: 1609459200000000000,
        end_date_unix_nanos: 1640995200000000000,
        initial_capital: 100000.0,
        parameters: std::collections::HashMap::new(),
        save_results: true,
        description: "Incomplete backtest test".to_string(),
    });

    let start_result = client.start_backtest(start_request).await?;
    let backtest_id = start_result.into_inner().backtest_id;

    // Immediately try to get results (should fail - backtest not complete)
    let results_request = Request::new(GetBacktestResultsRequest {
        backtest_id: backtest_id.clone(),
        include_trades: true,
        include_metrics: true,
    });

    let result = client.get_backtest_results(results_request).await;

    if result.is_err() {
        let status = result.unwrap_err();
        assert_eq!(
            status.code(),
            Code::FailedPrecondition,
            "Expected FailedPrecondition for incomplete backtest"
        );
        println!("  ✓ Get results for incomplete backtest correctly rejected");
    } else {
        println!("  ℹ Backtest completed too quickly or returns partial results");
    }

    // Clean up - stop the backtest
    let _ = client
        .stop_backtest(Request::new(StopBacktestRequest {
            backtest_id,
            save_partial_results: false,
        }))
        .await;

    Ok(())
}

#[tokio::test]
async fn test_stop_already_completed_backtest_returns_failed_precondition() -> Result<()> {
    println!("\n=== Test: Stop Backtest - Already Completed (FailedPrecondition) ===");

    let mut client = create_authenticated_client().await?;

    // Start a very short backtest that will complete quickly
    let start_request = Request::new(StartBacktestRequest {
        strategy_name: "test_strategy".to_string(),
        symbols: vec!["BTC/USD".to_string()],
        start_date_unix_nanos: 1609459200000000000,
        end_date_unix_nanos: 1609459200001000000, // 1ms duration
        initial_capital: 100000.0,
        parameters: std::collections::HashMap::new(),
        save_results: true,
        description: "Quick backtest".to_string(),
    });

    let start_result = client.start_backtest(start_request).await?;
    let backtest_id = start_result.into_inner().backtest_id;

    // Wait for backtest to complete
    tokio::time::sleep(Duration::from_secs(2)).await;

    // Try to stop already completed backtest
    let stop_request = Request::new(StopBacktestRequest {
        backtest_id,
        save_partial_results: false,
    });

    let result = client.stop_backtest(stop_request).await;

    if result.is_err() {
        let status = result.unwrap_err();
        if status.code() == Code::FailedPrecondition {
            println!("  ✓ Stop completed backtest correctly rejected");
        } else {
            println!("  ℹ Got error code: {:?}", status.code());
        }
    } else {
        println!("  ℹ Stop succeeded (idempotent operation)");
    }

    Ok(())
}

// ============================================================================
// RESOURCE EXHAUSTED TESTS (Too Many Jobs)
// ============================================================================

#[tokio::test]
#[ignore = "Slow test - requires many concurrent jobs"]
async fn test_start_backtest_too_many_concurrent_jobs_returns_resource_exhausted() -> Result<()> {
    println!("\n=== Test: Start Backtest - Too Many Concurrent Jobs (ResourceExhausted) ===");

    let mut client = create_authenticated_client().await?;

    // Start many backtests concurrently to exhaust resources
    let mut job_ids = vec![];
    let mut exhausted = false;

    for i in 0..50 {
        let request = Request::new(StartBacktestRequest {
            strategy_name: format!("stress_test_{}", i),
            symbols: vec!["BTC/USD".to_string()],
            start_date_unix_nanos: 1609459200000000000,
            end_date_unix_nanos: 1640995200000000000,
            initial_capital: 100000.0,
            parameters: std::collections::HashMap::new(),
            save_results: false,
            description: format!("Stress test job {}", i),
        });

        match client.start_backtest(request).await {
            Ok(response) => {
                job_ids.push(response.into_inner().backtest_id);
            },
            Err(status) => {
                if status.code() == Code::ResourceExhausted {
                    println!("  ✓ Resource exhaustion triggered after {} jobs", i);
                    exhausted = true;
                    break;
                }
            },
        }
    }

    // Clean up all started jobs
    for job_id in job_ids {
        let _ = client
            .stop_backtest(Request::new(StopBacktestRequest {
                backtest_id: job_id,
                save_partial_results: false,
            }))
            .await;
    }

    if !exhausted {
        println!("  ℹ Resource exhaustion not triggered (high capacity or small dataset)");
    }

    Ok(())
}

// ============================================================================
// INTERNAL ERROR TESTS (Data Loading Failures)
// ============================================================================

#[tokio::test]
async fn test_start_backtest_missing_market_data_returns_internal() -> Result<()> {
    println!("\n=== Test: Start Backtest - Missing Market Data (Internal) ===");

    let mut client = create_authenticated_client().await?;

    // Request backtest for future dates (no data available)
    // Using year 2100 (approximately 4102444800 seconds since epoch)
    // i64::MAX is 9223372036854775807, so we use values well within range
    let request = Request::new(StartBacktestRequest {
        strategy_name: "test_strategy".to_string(),
        symbols: vec!["BTC/USD".to_string()],
        start_date_unix_nanos: 4102444800000000000_i64, // 2100-01-01 (far future)
        end_date_unix_nanos: 4133980800000000000_i64,   // 2101-01-01
        initial_capital: 100000.0,
        parameters: std::collections::HashMap::new(),
        save_results: false,
        description: "Missing data test".to_string(),
    });

    let result = client.start_backtest(request).await;

    if result.is_err() {
        let status = result.unwrap_err();
        // Could be Internal (data loading error) or InvalidArgument (invalid date)
        assert!(
            status.code() == Code::Internal || status.code() == Code::InvalidArgument,
            "Expected Internal or InvalidArgument for missing data"
        );
        println!("  ✓ Missing market data correctly handled");
    } else {
        println!("  ℹ Request succeeded (may return empty results)");
    }

    Ok(())
}

// ============================================================================
// DEADLINE EXCEEDED TESTS (Long-running Jobs)
// ============================================================================

#[tokio::test]
async fn test_start_backtest_with_short_timeout_may_fail() -> Result<()> {
    println!("\n=== Test: Start Backtest - Short Timeout (DeadlineExceeded) ===");

    let channel = tonic::transport::Channel::from_static("http://localhost:50053")
        .timeout(Duration::from_micros(1)) // Very short timeout
        .connect()
        .await?;

    let token = create_valid_jwt_token()?;

    let mut client =
        BacktestingServiceClient::with_interceptor(channel, move |mut req: Request<()>| {
            req.metadata_mut().insert(
                "authorization",
                format!("Bearer {}", token).parse().expect("INVARIANT: Valid parse input"),
            );
            Ok(req)
        });

    let request = Request::new(StartBacktestRequest {
        strategy_name: "timeout_test".to_string(),
        symbols: vec!["BTC/USD".to_string()],
        start_date_unix_nanos: 1609459200000000000,
        end_date_unix_nanos: 1640995200000000000,
        initial_capital: 100000.0,
        parameters: std::collections::HashMap::new(),
        save_results: false,
        description: "Timeout test".to_string(),
    });

    let result = client.start_backtest(request).await;

    if result.is_err() {
        let status = result.unwrap_err();
        if status.code() == Code::DeadlineExceeded {
            println!("  ✓ Request timed out as expected");
        } else {
            println!("  ℹ Request failed with: {:?}", status.code());
        }
    } else {
        println!("  ℹ Request completed within deadline");
    }

    Ok(())
}
