//! Comprehensive gRPC service tests for backtesting service
//!
//! Tests all RPC endpoints with proper mock repositories and edge cases

use std::collections::HashMap;
use std::sync::Arc;
use tokio::time::{sleep, Duration};
use tonic::Request;

use backtesting_service::foxhunt::tli::{
    backtesting_service_server::BacktestingService, BacktestStatus, GetBacktestResultsRequest,
    GetBacktestStatusRequest, ListBacktestsRequest, StartBacktestRequest, StopBacktestRequest,
    SubscribeBacktestProgressRequest,
};
use backtesting_service::repositories::BacktestingRepositories;
use backtesting_service::service::BacktestingServiceImpl;

mod mock_repositories;
use mock_repositories::*;

/// Helper function to create test service with mock repositories
async fn create_test_service(
) -> Result<BacktestingServiceImpl, Box<dyn std::error::Error + Send + Sync>> {
    let market_data =
        MockMarketDataRepository::with_data(generate_sample_market_data("AAPL", 100, 150.0, 0.02));
    let trading = MockTradingRepository::new();
    let news =
        MockNewsRepository::with_events(generate_sample_news_events(&["AAPL".to_string()], 20));

    let repos: Arc<dyn BacktestingRepositories> = Arc::new(MockBacktestingRepositories::new(
        Box::new(market_data),
        Box::new(trading),
        Box::new(news),
    ));

    Ok(BacktestingServiceImpl::new(repos).await?)
}

// ============================================================================
// START BACKTEST TESTS
// ============================================================================

#[tokio::test]
async fn test_start_backtest_success() {
    let service = create_test_service()
        .await
        .expect("Failed to create service");

    let request = Request::new(StartBacktestRequest {
        strategy_name: "momentum".to_string(),
        symbols: vec!["AAPL".to_string()],
        start_date_unix_nanos: 1_600_000_000_000_000_000, // Sep 2020
        end_date_unix_nanos: 1_610_000_000_000_000_000,   // Jan 2021
        initial_capital: 100_000.0,
        parameters: HashMap::new(),
        save_results: false,
        description: "Test backtest".to_string(),
    });

    let response = service.start_backtest(request).await.expect("RPC failed");
    let result = response.into_inner();

    assert!(result.success, "Backtest should start successfully");
    assert!(!result.backtest_id.is_empty(), "Should have backtest ID");
    assert!(
        result.estimated_duration_seconds > 0,
        "Should have duration estimate"
    );
}

#[tokio::test]
async fn test_start_backtest_invalid_strategy_name() {
    let service = create_test_service()
        .await
        .expect("Failed to create service");

    let request = Request::new(StartBacktestRequest {
        strategy_name: "".to_string(), // Invalid: empty strategy name
        symbols: vec!["AAPL".to_string()],
        start_date_unix_nanos: 1_600_000_000_000_000_000,
        end_date_unix_nanos: 1_610_000_000_000_000_000,
        initial_capital: 100_000.0,
        parameters: HashMap::new(),
        save_results: false,
        description: "Test".to_string(),
    });

    let result = service.start_backtest(request).await;
    assert!(result.is_err(), "Should fail with empty strategy name");

    let err = result.unwrap_err();
    assert_eq!(err.code(), tonic::Code::InvalidArgument);
    assert!(err.message().contains("Strategy name"));
}

#[tokio::test]
async fn test_start_backtest_no_symbols() {
    let service = create_test_service()
        .await
        .expect("Failed to create service");

    let request = Request::new(StartBacktestRequest {
        strategy_name: "momentum".to_string(),
        symbols: vec![], // Invalid: no symbols
        start_date_unix_nanos: 1_600_000_000_000_000_000,
        end_date_unix_nanos: 1_610_000_000_000_000_000,
        initial_capital: 100_000.0,
        parameters: HashMap::new(),
        save_results: false,
        description: "Test".to_string(),
    });

    let result = service.start_backtest(request).await;
    assert!(result.is_err(), "Should fail with no symbols");

    let err = result.unwrap_err();
    assert_eq!(err.code(), tonic::Code::InvalidArgument);
    assert!(err.message().contains("symbol"));
}

#[tokio::test]
async fn test_start_backtest_invalid_capital() {
    let service = create_test_service()
        .await
        .expect("Failed to create service");

    let request = Request::new(StartBacktestRequest {
        strategy_name: "momentum".to_string(),
        symbols: vec!["AAPL".to_string()],
        start_date_unix_nanos: 1_600_000_000_000_000_000,
        end_date_unix_nanos: 1_610_000_000_000_000_000,
        initial_capital: -1000.0, // Invalid: negative capital
        parameters: HashMap::new(),
        save_results: false,
        description: "Test".to_string(),
    });

    let result = service.start_backtest(request).await;
    assert!(result.is_err(), "Should fail with negative capital");

    let err = result.unwrap_err();
    assert_eq!(err.code(), tonic::Code::InvalidArgument);
    assert!(err.message().contains("capital"));
}

#[tokio::test]
async fn test_start_backtest_invalid_date_range() {
    let service = create_test_service()
        .await
        .expect("Failed to create service");

    let request = Request::new(StartBacktestRequest {
        strategy_name: "momentum".to_string(),
        symbols: vec!["AAPL".to_string()],
        start_date_unix_nanos: 1_610_000_000_000_000_000, // Later date
        end_date_unix_nanos: 1_600_000_000_000_000_000,   // Earlier date
        initial_capital: 100_000.0,
        parameters: HashMap::new(),
        save_results: false,
        description: "Test".to_string(),
    });

    let result = service.start_backtest(request).await;
    assert!(result.is_err(), "Should fail with invalid date range");

    let err = result.unwrap_err();
    assert_eq!(err.code(), tonic::Code::InvalidArgument);
    assert!(err.message().contains("date"));
}

#[tokio::test]
async fn test_start_backtest_with_parameters() {
    let service = create_test_service()
        .await
        .expect("Failed to create service");

    let mut parameters = HashMap::new();
    parameters.insert("lookback".to_string(), "20".to_string());
    parameters.insert("threshold".to_string(), "0.02".to_string());

    let request = Request::new(StartBacktestRequest {
        strategy_name: "momentum".to_string(),
        symbols: vec!["AAPL".to_string()],
        start_date_unix_nanos: 1_600_000_000_000_000_000,
        end_date_unix_nanos: 1_610_000_000_000_000_000,
        initial_capital: 100_000.0,
        parameters,
        save_results: true,
        description: "Test with params".to_string(),
    });

    let response = service.start_backtest(request).await.expect("RPC failed");
    let result = response.into_inner();

    assert!(result.success);
    assert!(!result.backtest_id.is_empty());
}

// ============================================================================
// GET BACKTEST STATUS TESTS
// ============================================================================

#[tokio::test]
async fn test_get_backtest_status_success() {
    let service = create_test_service()
        .await
        .expect("Failed to create service");

    // Start a backtest first
    let start_req = Request::new(StartBacktestRequest {
        strategy_name: "momentum".to_string(),
        symbols: vec!["AAPL".to_string()],
        start_date_unix_nanos: 1_600_000_000_000_000_000,
        end_date_unix_nanos: 1_610_000_000_000_000_000,
        initial_capital: 100_000.0,
        parameters: HashMap::new(),
        save_results: false,
        description: "Test".to_string(),
    });

    let start_resp = service
        .start_backtest(start_req)
        .await
        .expect("Start failed");
    let backtest_id = start_resp.into_inner().backtest_id;

    // Get status
    let status_req = Request::new(GetBacktestStatusRequest {
        backtest_id: backtest_id.clone(),
    });

    let status_resp = service
        .get_backtest_status(status_req)
        .await
        .expect("Status failed");
    let status = status_resp.into_inner();

    assert_eq!(status.backtest_id, backtest_id);
    assert!(status.status != BacktestStatus::Unspecified as i32);
    assert!(status.started_at_unix_nanos > 0);
}

#[tokio::test]
async fn test_get_backtest_status_not_found() {
    let service = create_test_service()
        .await
        .expect("Failed to create service");

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

    let result = service.get_backtest_status(request).await;
    assert!(result.is_err(), "Should fail for non-existent backtest");

    let err = result.unwrap_err();
    assert_eq!(err.code(), tonic::Code::NotFound);
}

// ============================================================================
// GET BACKTEST RESULTS TESTS
// ============================================================================

#[tokio::test]
async fn test_get_backtest_results_not_completed() {
    let service = create_test_service()
        .await
        .expect("Failed to create service");

    // Start a backtest
    let start_req = Request::new(StartBacktestRequest {
        strategy_name: "momentum".to_string(),
        symbols: vec!["AAPL".to_string()],
        start_date_unix_nanos: 1_600_000_000_000_000_000,
        end_date_unix_nanos: 1_610_000_000_000_000_000,
        initial_capital: 100_000.0,
        parameters: HashMap::new(),
        save_results: true,
        description: "Test".to_string(),
    });

    let start_resp = service
        .start_backtest(start_req)
        .await
        .expect("Start failed");
    let backtest_id = start_resp.into_inner().backtest_id;

    // Try to get results immediately (should fail - not completed)
    let results_req = Request::new(GetBacktestResultsRequest {
        backtest_id,
        include_trades: true,
        include_metrics: true,
    });

    let result = service.get_backtest_results(results_req).await;
    assert!(result.is_err(), "Should fail for incomplete backtest");

    let err = result.unwrap_err();
    assert_eq!(err.code(), tonic::Code::FailedPrecondition);
}

#[tokio::test]
async fn test_get_backtest_results_not_found() {
    let service = create_test_service()
        .await
        .expect("Failed to create service");

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

    let result = service.get_backtest_results(request).await;
    assert!(result.is_err(), "Should fail for non-existent backtest");

    let err = result.unwrap_err();
    assert_eq!(err.code(), tonic::Code::NotFound);
}

#[tokio::test]
async fn test_get_backtest_results_exclude_trades() {
    let service = create_test_service()
        .await
        .expect("Failed to create service");

    // Start a backtest with save_results = true
    let mut params = HashMap::new();
    params.insert("save_results".to_string(), "true".to_string());

    let start_req = Request::new(StartBacktestRequest {
        strategy_name: "momentum".to_string(),
        symbols: vec!["AAPL".to_string()],
        start_date_unix_nanos: 1_600_000_000_000_000_000,
        end_date_unix_nanos: 1_610_000_000_000_000_000,
        initial_capital: 100_000.0,
        parameters: params,
        save_results: true,
        description: "Test".to_string(),
    });

    let start_resp = service
        .start_backtest(start_req)
        .await
        .expect("Start failed");
    let backtest_id = start_resp.into_inner().backtest_id;

    // Wait for backtest to complete (in practice, mock would complete instantly)
    sleep(Duration::from_millis(100)).await;

    // Request results without trades
    let results_req = Request::new(GetBacktestResultsRequest {
        backtest_id,
        include_trades: false,
        include_metrics: true,
    });

    // This test would pass if backtest completes and results are saved
    // For now, we verify the request structure
    let _ = service.get_backtest_results(results_req).await;
}

// ============================================================================
// LIST BACKTESTS TESTS
// ============================================================================

#[tokio::test]
async fn test_list_backtests_empty() {
    let service = create_test_service()
        .await
        .expect("Failed to create service");

    let request = Request::new(ListBacktestsRequest {
        limit: 10,
        offset: 0,
        strategy_name: None,
        status_filter: None,
    });

    let response = service.list_backtests(request).await.expect("List failed");
    let result = response.into_inner();

    // Initially empty
    assert_eq!(result.backtests.len(), 0);
}

#[tokio::test]
async fn test_list_backtests_with_filter() {
    let service = create_test_service()
        .await
        .expect("Failed to create service");

    let request = Request::new(ListBacktestsRequest {
        limit: 10,
        offset: 0,
        strategy_name: Some("momentum".to_string()),
        status_filter: Some(BacktestStatus::Completed as i32),
    });

    let response = service.list_backtests(request).await.expect("List failed");
    let _result = response.into_inner();

    // Valid request structure verified
}

#[tokio::test]
async fn test_list_backtests_pagination() {
    let service = create_test_service()
        .await
        .expect("Failed to create service");

    // First page
    let request1 = Request::new(ListBacktestsRequest {
        limit: 5,
        offset: 0,
        strategy_name: None,
        status_filter: None,
    });

    let response1 = service.list_backtests(request1).await.expect("List failed");
    let result1 = response1.into_inner();

    // Second page
    let request2 = Request::new(ListBacktestsRequest {
        limit: 5,
        offset: 5,
        strategy_name: None,
        status_filter: None,
    });

    let response2 = service.list_backtests(request2).await.expect("List failed");
    let result2 = response2.into_inner();

    // Both should succeed (even if empty)
    assert!(result1.backtests.len() <= 5);
    assert!(result2.backtests.len() <= 5);
}

// ============================================================================
// SUBSCRIBE BACKTEST PROGRESS TESTS
// ============================================================================

#[tokio::test]
async fn test_subscribe_backtest_progress_not_found() {
    let service = create_test_service()
        .await
        .expect("Failed to create service");

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

    let result = service.subscribe_backtest_progress(request).await;
    assert!(result.is_err(), "Should fail for non-existent backtest");

    match result {
        Err(err) => assert_eq!(err.code(), tonic::Code::NotFound),
        Ok(_) => panic!("Expected error for non-existent backtest"),
    }
}

#[tokio::test]
async fn test_subscribe_backtest_progress_success() {
    let service = create_test_service()
        .await
        .expect("Failed to create service");

    // Start a backtest first
    let start_req = Request::new(StartBacktestRequest {
        strategy_name: "momentum".to_string(),
        symbols: vec!["AAPL".to_string()],
        start_date_unix_nanos: 1_600_000_000_000_000_000,
        end_date_unix_nanos: 1_610_000_000_000_000_000,
        initial_capital: 100_000.0,
        parameters: HashMap::new(),
        save_results: false,
        description: "Test".to_string(),
    });

    let start_resp = service
        .start_backtest(start_req)
        .await
        .expect("Start failed");
    let backtest_id = start_resp.into_inner().backtest_id;

    // Subscribe to progress
    let subscribe_req = Request::new(SubscribeBacktestProgressRequest {
        backtest_id: backtest_id.clone(),
    });

    let result = service.subscribe_backtest_progress(subscribe_req).await;
    assert!(result.is_ok(), "Should succeed for existing backtest");

    // Stream created successfully
    let _stream = result.unwrap().into_inner();
}

// ============================================================================
// STOP BACKTEST TESTS
// ============================================================================

#[tokio::test]
async fn test_stop_backtest_success() {
    let service = create_test_service()
        .await
        .expect("Failed to create service");

    // Start a backtest first
    let start_req = Request::new(StartBacktestRequest {
        strategy_name: "momentum".to_string(),
        symbols: vec!["AAPL".to_string()],
        start_date_unix_nanos: 1_600_000_000_000_000_000,
        end_date_unix_nanos: 1_610_000_000_000_000_000,
        initial_capital: 100_000.0,
        parameters: HashMap::new(),
        save_results: false,
        description: "Test".to_string(),
    });

    let start_resp = service
        .start_backtest(start_req)
        .await
        .expect("Start failed");
    let backtest_id = start_resp.into_inner().backtest_id;

    // Stop the backtest
    let stop_req = Request::new(StopBacktestRequest {
        backtest_id: backtest_id.clone(),
        save_partial_results: false,
    });

    let stop_resp = service.stop_backtest(stop_req).await.expect("Stop failed");
    let result = stop_resp.into_inner();

    assert!(result.success, "Stop should succeed");
    assert!(!result.message.is_empty(), "Should have message");
}

#[tokio::test]
async fn test_stop_backtest_not_found() {
    let service = create_test_service()
        .await
        .expect("Failed to create service");

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

    let result = service.stop_backtest(request).await;
    assert!(result.is_err(), "Should fail for non-existent backtest");

    let err = result.unwrap_err();
    assert_eq!(err.code(), tonic::Code::NotFound);
}

#[tokio::test]
async fn test_stop_backtest_with_partial_save() {
    let service = create_test_service()
        .await
        .expect("Failed to create service");

    // Start a backtest
    let start_req = Request::new(StartBacktestRequest {
        strategy_name: "momentum".to_string(),
        symbols: vec!["AAPL".to_string()],
        start_date_unix_nanos: 1_600_000_000_000_000_000,
        end_date_unix_nanos: 1_610_000_000_000_000_000,
        initial_capital: 100_000.0,
        parameters: HashMap::new(),
        save_results: true,
        description: "Test".to_string(),
    });

    let start_resp = service
        .start_backtest(start_req)
        .await
        .expect("Start failed");
    let backtest_id = start_resp.into_inner().backtest_id;

    // Stop with partial save
    let stop_req = Request::new(StopBacktestRequest {
        backtest_id: backtest_id.clone(),
        save_partial_results: true,
    });

    let stop_resp = service.stop_backtest(stop_req).await.expect("Stop failed");
    let result = stop_resp.into_inner();

    assert!(result.success);
    assert!(result.results_saved);
}

// ============================================================================
// CONCURRENT BACKTEST TESTS
// ============================================================================

#[tokio::test]
async fn test_concurrent_backtests() {
    let service = Arc::new(
        create_test_service()
            .await
            .expect("Failed to create service"),
    );

    let mut handles = vec![];

    // Start 5 concurrent backtests
    for i in 0..5 {
        let service_clone = service.clone();
        let handle = tokio::spawn(async move {
            let request = Request::new(StartBacktestRequest {
                strategy_name: format!("strategy_{}", i),
                symbols: vec!["AAPL".to_string()],
                start_date_unix_nanos: 1_600_000_000_000_000_000,
                end_date_unix_nanos: 1_610_000_000_000_000_000,
                initial_capital: 100_000.0,
                parameters: HashMap::new(),
                save_results: false,
                description: format!("Concurrent test {}", i),
            });

            service_clone.start_backtest(request).await
        });
        handles.push(handle);
    }

    // Wait for all to complete
    let mut success_count = 0;
    for handle in handles {
        if let Ok(Ok(response)) = handle.await {
            let result = response.into_inner();
            if result.success {
                success_count += 1;
            }
        }
    }

    assert_eq!(success_count, 5, "All concurrent backtests should succeed");
}

#[tokio::test]
async fn test_max_concurrent_backtests_limit() {
    let service = create_test_service()
        .await
        .expect("Failed to create service");

    let mut backtest_ids = vec![];

    // Start backtests up to the limit (10 by default in service.rs)
    for i in 0..10 {
        let request = Request::new(StartBacktestRequest {
            strategy_name: format!("strategy_{}", i),
            symbols: vec!["AAPL".to_string()],
            start_date_unix_nanos: 1_600_000_000_000_000_000,
            end_date_unix_nanos: 1_610_000_000_000_000_000,
            initial_capital: 100_000.0,
            parameters: HashMap::new(),
            save_results: false,
            description: format!("Limit test {}", i),
        });

        let response = service.start_backtest(request).await.expect("Start failed");
        backtest_ids.push(response.into_inner().backtest_id);
    }

    // The 11th should fail with resource exhausted
    let request = Request::new(StartBacktestRequest {
        strategy_name: "strategy_11".to_string(),
        symbols: vec!["AAPL".to_string()],
        start_date_unix_nanos: 1_600_000_000_000_000_000,
        end_date_unix_nanos: 1_610_000_000_000_000_000,
        initial_capital: 100_000.0,
        parameters: HashMap::new(),
        save_results: false,
        description: "Should fail".to_string(),
    });

    let result = service.start_backtest(request).await;
    assert!(result.is_err(), "Should fail when limit reached");

    let err = result.unwrap_err();
    assert_eq!(err.code(), tonic::Code::ResourceExhausted);
}

// ============================================================================
// INTEGRATION TESTS - FULL WORKFLOW
// ============================================================================

#[tokio::test]
async fn test_full_backtest_workflow() {
    let service = create_test_service()
        .await
        .expect("Failed to create service");

    // 1. Start backtest
    let start_req = Request::new(StartBacktestRequest {
        strategy_name: "momentum".to_string(),
        symbols: vec!["AAPL".to_string()],
        start_date_unix_nanos: 1_600_000_000_000_000_000,
        end_date_unix_nanos: 1_610_000_000_000_000_000,
        initial_capital: 100_000.0,
        parameters: HashMap::new(),
        save_results: true,
        description: "Full workflow test".to_string(),
    });

    let start_resp = service
        .start_backtest(start_req)
        .await
        .expect("Start failed");
    let backtest_id = start_resp.into_inner().backtest_id;
    assert!(!backtest_id.is_empty());

    // 2. Check status
    let status_req = Request::new(GetBacktestStatusRequest {
        backtest_id: backtest_id.clone(),
    });

    let status_resp = service
        .get_backtest_status(status_req)
        .await
        .expect("Status failed");
    let status = status_resp.into_inner();
    assert_eq!(status.backtest_id, backtest_id);

    // 3. Subscribe to progress
    let subscribe_req = Request::new(SubscribeBacktestProgressRequest {
        backtest_id: backtest_id.clone(),
    });

    let subscribe_result = service.subscribe_backtest_progress(subscribe_req).await;
    assert!(subscribe_result.is_ok(), "Subscribe should succeed");

    // 4. List backtests (should include ours)
    let list_req = Request::new(ListBacktestsRequest {
        limit: 10,
        offset: 0,
        strategy_name: None,
        status_filter: None,
    });

    let _list_resp = service.list_backtests(list_req).await.expect("List failed");
}
