//! gRPC Endpoints Integration Tests
//!
//! Comprehensive tests for gRPC service endpoints covering:
//! - Request/Response validation
//! - Error handling and status codes
//! - Streaming endpoints (orders, positions, market data, executions)
//! - Concurrent requests
//! - Malformed request handling
//! - Timeout and error scenarios

use anyhow::Result;
use std::sync::Arc;
use std::time::Duration;
use tokio_stream::StreamExt;
use tonic::Request;
use trading_service::proto::trading::{
    trading_service_server::TradingService, CancelOrderRequest, GetExecutionHistoryRequest,
    GetOrderBookRequest, GetOrderStatusRequest, GetPortfolioSummaryRequest, GetPositionsRequest,
    MarketDataType, OrderSide, OrderType, StreamExecutionsRequest, StreamMarketDataRequest,
    StreamOrdersRequest, StreamPositionsRequest, SubmitOrderRequest,
};
use trading_service::{services::trading::TradingServiceImpl, state::TradingServiceState};

/// Setup test trading service instance
async fn setup_trading_service() -> Result<TradingServiceImpl> {
    let state = Arc::new(TradingServiceState::new_for_testing().await?);
    Ok(TradingServiceImpl::new(state))
}

// ============================================================================
// Request/Response Validation Tests
// ============================================================================

#[tokio::test]
async fn test_submit_order_endpoint_validation() -> Result<()> {
    println!("\n=== Test: SubmitOrder Endpoint Validation ===");

    let service = setup_trading_service().await?;

    let mut metadata = std::collections::HashMap::new();
    metadata.insert("time_in_force".to_string(), "GTC".to_string());

    let request = Request::new(SubmitOrderRequest {
        account_id: "grpc_test_001".to_string(),
        symbol: "AAPL".to_string(),
        side: OrderSide::Buy as i32,
        order_type: OrderType::Market as i32,
        quantity: 100.0,
        price: None,
        stop_price: None,
        metadata,
    });

    let response = service.submit_order(request).await?;
    let order = response.into_inner();

    println!("  ✓ Response received");
    println!("    ├─ Order ID: {}", order.order_id);
    println!("    ├─ Status: {:?}", order.status);
    println!("    └─ Message: {}", order.message);

    assert!(!order.order_id.is_empty());
    assert!(order.timestamp > 0);

    Ok(())
}

#[tokio::test]
async fn test_cancel_order_endpoint() -> Result<()> {
    println!("\n=== Test: CancelOrder Endpoint ===");

    let service = setup_trading_service().await?;

    // First submit an order
    let mut metadata = std::collections::HashMap::new();
    metadata.insert("time_in_force".to_string(), "GTC".to_string());

    let submit_request = Request::new(SubmitOrderRequest {
        account_id: "grpc_test_002".to_string(),
        symbol: "GOOGL".to_string(),
        side: OrderSide::Buy as i32,
        order_type: OrderType::Limit as i32,
        quantity: 50.0,
        price: Some(150.00),
        stop_price: None,
        metadata,
    });

    let submit_response = service.submit_order(submit_request).await?;
    let order_id = submit_response.into_inner().order_id;

    // Cancel the order
    let cancel_request = Request::new(CancelOrderRequest {
        order_id: order_id.clone(),
        account_id: "grpc_test_002".to_string(),
    });

    let cancel_response = service.cancel_order(cancel_request).await?;
    let cancel_result = cancel_response.into_inner();

    println!("  ✓ Cancellation response received");
    println!("    ├─ Success: {}", cancel_result.success);
    println!("    ├─ Message: {}", cancel_result.message);
    println!("    └─ Timestamp: {}", cancel_result.timestamp);

    assert!(cancel_result.timestamp > 0);

    Ok(())
}

#[tokio::test]
async fn test_get_order_status_endpoint() -> Result<()> {
    println!("\n=== Test: GetOrderStatus Endpoint ===");

    let service = setup_trading_service().await?;

    // Submit order
    let mut metadata = std::collections::HashMap::new();
    metadata.insert("time_in_force".to_string(), "GTC".to_string());

    let submit_request = Request::new(SubmitOrderRequest {
        account_id: "grpc_test_003".to_string(),
        symbol: "MSFT".to_string(),
        side: OrderSide::Buy as i32,
        order_type: OrderType::Market as i32,
        quantity: 100.0,
        price: None,
        stop_price: None,
        metadata,
    });

    let submit_response = service.submit_order(submit_request).await?;
    let order_id = submit_response.into_inner().order_id;

    // Get status
    let status_request = Request::new(GetOrderStatusRequest {
        order_id: order_id.clone(),
    });

    let status_response = service.get_order_status(status_request).await?;
    let status = status_response.into_inner();

    println!("  ✓ Order status received");
    if let Some(order) = status.order {
        println!("    ├─ Order ID: {}", order.order_id);
        println!("    ├─ Symbol: {}", order.symbol);
        println!("    ├─ Quantity: {}", order.quantity);
        println!("    ├─ Status: {:?}", order.status);
        println!("    └─ Filled: {}", order.filled_quantity);
    }

    Ok(())
}

#[tokio::test]
async fn test_get_positions_endpoint() -> Result<()> {
    println!("\n=== Test: GetPositions Endpoint ===");

    let service = setup_trading_service().await?;

    let request = Request::new(GetPositionsRequest {
        account_id: Some("grpc_test_004".to_string()),
        symbol: None,
    });

    let response = service.get_positions(request).await?;
    let positions = response.into_inner();

    println!("  ✓ Positions response received");
    println!("    └─ Positions count: {}", positions.positions.len());

    for pos in &positions.positions {
        println!(
            "      ├─ {}: {} shares @ ${:.2}",
            pos.symbol, pos.quantity, pos.average_price
        );
    }

    Ok(())
}

#[tokio::test]
async fn test_get_portfolio_summary_endpoint() -> Result<()> {
    println!("\n=== Test: GetPortfolioSummary Endpoint ===");

    let service = setup_trading_service().await?;

    let request = Request::new(GetPortfolioSummaryRequest {
        account_id: "grpc_test_005".to_string(),
    });

    let response = service.get_portfolio_summary(request).await?;
    let summary = response.into_inner();

    println!("  ✓ Portfolio summary received");
    println!("    ├─ Total Value: ${:.2}", summary.total_value);
    println!("    ├─ Unrealized PnL: ${:.2}", summary.unrealized_pnl);
    println!("    ├─ Realized PnL: ${:.2}", summary.realized_pnl);
    println!("    ├─ Day PnL: ${:.2}", summary.day_pnl);
    println!("    ├─ Buying Power: ${:.2}", summary.buying_power);
    println!("    ├─ Margin Used: ${:.2}", summary.margin_used);
    println!("    └─ Positions: {}", summary.positions.len());

    Ok(())
}

#[tokio::test]
async fn test_get_order_book_endpoint() -> Result<()> {
    println!("\n=== Test: GetOrderBook Endpoint ===");

    let service = setup_trading_service().await?;

    let request = Request::new(GetOrderBookRequest {
        symbol: "AAPL".to_string(),
        depth: Some(10),
    });

    let response = service.get_order_book(request).await?;
    let order_book_response = response.into_inner();

    println!("  ✓ Order book response received");
    if let Some(order_book) = order_book_response.order_book {
        println!("    ├─ Symbol: {}", order_book.symbol);
        println!("    ├─ Bids: {}", order_book.bids.len());
        println!("    ├─ Asks: {}", order_book.asks.len());
        println!("    └─ Timestamp: {}", order_book.timestamp);

        if !order_book.bids.is_empty() {
            println!(
                "      Best Bid: ${:.2} x {}",
                order_book.bids[0].price, order_book.bids[0].quantity
            );
        }
        if !order_book.asks.is_empty() {
            println!(
                "      Best Ask: ${:.2} x {}",
                order_book.asks[0].price, order_book.asks[0].quantity
            );
        }
    }

    Ok(())
}

#[tokio::test]
async fn test_get_execution_history_endpoint() -> Result<()> {
    println!("\n=== Test: GetExecutionHistory Endpoint ===");

    let service = setup_trading_service().await?;

    let request = Request::new(GetExecutionHistoryRequest {
        account_id: Some("grpc_test_006".to_string()),
        symbol: None,
        start_time: None,
        end_time: None,
        limit: Some(50),
    });

    let response = service.get_execution_history(request).await?;
    let history = response.into_inner();

    println!("  ✓ Execution history received");
    println!("    └─ Executions: {}", history.executions.len());

    for (i, exec) in history.executions.into_iter().take(5).enumerate() {
        println!(
            "      {}. {}: {} @ ${:.2}",
            i + 1,
            exec.symbol,
            exec.quantity,
            exec.price
        );
    }

    Ok(())
}

// ============================================================================
// Streaming Endpoint Tests
// ============================================================================

#[tokio::test]
async fn test_stream_orders_endpoint() -> Result<()> {
    println!("\n=== Test: StreamOrders Endpoint ===");

    let service = setup_trading_service().await?;

    let request = Request::new(StreamOrdersRequest {
        account_id: Some("grpc_test_007".to_string()),
        symbol: None,
    });

    let mut stream = service.stream_orders(request).await?.into_inner();

    println!("  ✓ Order stream established");

    // Collect a few events with timeout
    let timeout_duration = Duration::from_secs(2);
    let timeout_future = tokio::time::sleep(timeout_duration);
    tokio::pin!(timeout_future);

    let mut event_count = 0;
    loop {
        tokio::select! {
            event_result = stream.next() => {
                match event_result {
                    Some(Ok(event)) => {
                        event_count += 1;
                        println!("    Event {}: Order {} - {:?}",
                            event_count, event.order_id, event.event_type);
                        if event_count >= 5 {
                            break;
                        }
                    }
                    Some(Err(e)) => {
                        println!("    Stream error: {}", e);
                        break;
                    }
                    None => {
                        println!("    Stream ended");
                        break;
                    }
                }
            }
            _ = &mut timeout_future => {
                println!("    Timeout after {} events", event_count);
                break;
            }
        }
    }

    println!("  Total events received: {}", event_count);
    Ok(())
}

#[tokio::test]
async fn test_stream_positions_endpoint() -> Result<()> {
    println!("\n=== Test: StreamPositions Endpoint ===");

    let service = setup_trading_service().await?;

    let request = Request::new(StreamPositionsRequest {
        account_id: Some("grpc_test_008".to_string()),
    });

    let mut stream = service.stream_positions(request).await?.into_inner();

    println!("  ✓ Position stream established");

    let timeout_duration = Duration::from_secs(2);
    let timeout_future = tokio::time::sleep(timeout_duration);
    tokio::pin!(timeout_future);

    let mut event_count = 0;
    loop {
        tokio::select! {
            event_result = stream.next() => {
                match event_result {
                    Some(Ok(event)) => {
                        event_count += 1;
                        if let Some(position) = event.position {
                            println!("    Event {}: {} - {} shares",
                                event_count, position.symbol, position.quantity);
                        }
                        if event_count >= 5 {
                            break;
                        }
                    }
                    Some(Err(e)) => {
                        println!("    Stream error: {}", e);
                        break;
                    }
                    None => {
                        println!("    Stream ended");
                        break;
                    }
                }
            }
            _ = &mut timeout_future => {
                println!("    Timeout after {} events", event_count);
                break;
            }
        }
    }

    println!("  Total events received: {}", event_count);
    Ok(())
}

#[tokio::test]
async fn test_stream_executions_endpoint() -> Result<()> {
    println!("\n=== Test: StreamExecutions Endpoint ===");

    let service = setup_trading_service().await?;

    let request = Request::new(StreamExecutionsRequest {
        account_id: Some("grpc_test_009".to_string()),
        symbol: None,
    });

    let mut stream = service.stream_executions(request).await?.into_inner();

    println!("  ✓ Execution stream established");

    let timeout_duration = Duration::from_secs(2);
    let timeout_future = tokio::time::sleep(timeout_duration);
    tokio::pin!(timeout_future);

    let mut event_count = 0;
    loop {
        tokio::select! {
            event_result = stream.next() => {
                match event_result {
                    Some(Ok(event)) => {
                        event_count += 1;
                        println!("    Event {}: {} - {} @ ${:.2}",
                            event_count, event.symbol, event.quantity, event.price);
                        if event_count >= 5 {
                            break;
                        }
                    }
                    Some(Err(e)) => {
                        println!("    Stream error: {}", e);
                        break;
                    }
                    None => {
                        println!("    Stream ended");
                        break;
                    }
                }
            }
            _ = &mut timeout_future => {
                println!("    Timeout after {} events", event_count);
                break;
            }
        }
    }

    println!("  Total events received: {}", event_count);
    Ok(())
}

#[tokio::test]
async fn test_stream_market_data_endpoint() -> Result<()> {
    println!("\n=== Test: StreamMarketData Endpoint ===");

    let service = setup_trading_service().await?;

    let request = Request::new(StreamMarketDataRequest {
        symbols: vec!["AAPL".to_string(), "GOOGL".to_string()],
        data_types: vec![MarketDataType::Trade as i32, MarketDataType::Quote as i32],
    });

    let mut stream = service.stream_market_data(request).await?.into_inner();

    println!("  ✓ Market data stream established");

    let timeout_duration = Duration::from_secs(2);
    let timeout_future = tokio::time::sleep(timeout_duration);
    tokio::pin!(timeout_future);

    let mut event_count = 0;
    loop {
        tokio::select! {
            event_result = stream.next() => {
                match event_result {
                    Some(Ok(event)) => {
                        event_count += 1;
                        println!("    Event {}: {} - Type: {:?}",
                            event_count, event.symbol, event.data_type);
                        if event_count >= 5 {
                            break;
                        }
                    }
                    Some(Err(e)) => {
                        println!("    Stream error: {}", e);
                        break;
                    }
                    None => {
                        println!("    Stream ended");
                        break;
                    }
                }
            }
            _ = &mut timeout_future => {
                println!("    Timeout after {} events", event_count);
                break;
            }
        }
    }

    println!("  Total events received: {}", event_count);
    Ok(())
}

// ============================================================================
// Error Handling Tests
// ============================================================================

#[tokio::test]
async fn test_invalid_order_side_error() -> Result<()> {
    println!("\n=== Test: Invalid Order Side Error ===");

    let service = setup_trading_service().await?;

    let request = Request::new(SubmitOrderRequest {
        account_id: "grpc_test_010".to_string(),
        symbol: "AAPL".to_string(),
        side: 999, // Invalid side
        order_type: OrderType::Market as i32,
        quantity: 100.0,
        price: None,
        stop_price: None,
        metadata: std::collections::HashMap::new(),
    });

    let result = service.submit_order(request).await;

    match result {
        Ok(_) => println!("  Request accepted (validation might be lenient)"),
        Err(status) => {
            println!("  ✓ Error returned: {}", status.message());
            assert!(
                status.code() == tonic::Code::InvalidArgument
                    || status.code() == tonic::Code::FailedPrecondition
            );
        },
    }

    Ok(())
}

#[tokio::test]
async fn test_empty_account_id_error() -> Result<()> {
    println!("\n=== Test: Empty Account ID Error ===");

    let service = setup_trading_service().await?;

    let request = Request::new(SubmitOrderRequest {
        account_id: "".to_string(), // Empty account ID
        symbol: "AAPL".to_string(),
        side: OrderSide::Buy as i32,
        order_type: OrderType::Market as i32,
        quantity: 100.0,
        price: None,
        stop_price: None,
        metadata: std::collections::HashMap::new(),
    });

    let result = service.submit_order(request).await;

    match result {
        Ok(_) => println!("  Request accepted (validation might be lenient)"),
        Err(status) => {
            println!("  ✓ Error returned: {}", status.message());
            assert_eq!(status.code(), tonic::Code::InvalidArgument);
        },
    }

    Ok(())
}

#[tokio::test]
async fn test_nonexistent_order_status() -> Result<()> {
    println!("\n=== Test: Nonexistent Order Status ===");

    let service = setup_trading_service().await?;

    let request = Request::new(GetOrderStatusRequest {
        order_id: "nonexistent_order_12345".to_string(),
    });

    let result = service.get_order_status(request).await;

    match result {
        Ok(response) => {
            let status = response.into_inner();
            println!(
                "  Response received, order present: {}",
                status.order.is_some()
            );
        },
        Err(status) => {
            println!("  ✓ Error returned: {}", status.message());
            assert_eq!(status.code(), tonic::Code::NotFound);
        },
    }

    Ok(())
}

// ============================================================================
// Concurrent Request Tests
// ============================================================================

#[tokio::test]
async fn test_concurrent_endpoint_requests() -> Result<()> {
    println!("\n=== Test: Concurrent Endpoint Requests ===");

    let service = Arc::new(setup_trading_service().await?);
    let mut handles = vec![];

    // Submit multiple requests concurrently
    for i in 1..=10 {
        let svc = service.clone();
        let handle = tokio::spawn(async move {
            let mut metadata = std::collections::HashMap::new();
            metadata.insert("time_in_force".to_string(), "GTC".to_string());
            metadata.insert("client_id".to_string(), format!("concurrent_{}", i));

            let request = Request::new(SubmitOrderRequest {
                account_id: format!("grpc_test_{:03}", i),
                symbol: "SPY".to_string(),
                side: if i % 2 == 0 {
                    OrderSide::Buy as i32
                } else {
                    OrderSide::Sell as i32
                },
                order_type: OrderType::Market as i32,
                quantity: 10.0 * (i as f64),
                price: None,
                stop_price: None,
                metadata,
            });

            svc.submit_order(request).await
        });
        handles.push(handle);
    }

    // Wait for all requests
    let mut success_count = 0;
    for handle in handles {
        if let Ok(Ok(_)) = handle.await {
            success_count += 1;
        }
    }

    println!("  ✓ {}/10 concurrent requests succeeded", success_count);
    assert!(
        success_count >= 8,
        "Most concurrent requests should succeed"
    );

    Ok(())
}

#[tokio::test]
async fn test_mixed_endpoint_concurrent_access() -> Result<()> {
    println!("\n=== Test: Mixed Endpoint Concurrent Access ===");

    let service = Arc::new(setup_trading_service().await?);
    let mut handles = vec![];

    // Mix different endpoint calls
    for i in 1..=15 {
        let svc = service.clone();
        let handle = tokio::spawn(async move {
            match i % 3 {
                0 => {
                    // Submit order
                    let request = Request::new(SubmitOrderRequest {
                        account_id: "mixed_test".to_string(),
                        symbol: "AAPL".to_string(),
                        side: OrderSide::Buy as i32,
                        order_type: OrderType::Market as i32,
                        quantity: 10.0,
                        price: None,
                        stop_price: None,
                        metadata: std::collections::HashMap::new(),
                    });
                    svc.submit_order(request).await.is_ok()
                },
                1 => {
                    // Get positions
                    let request = Request::new(GetPositionsRequest {
                        account_id: Some("mixed_test".to_string()),
                        symbol: None,
                    });
                    svc.get_positions(request).await.is_ok()
                },
                _ => {
                    // Get portfolio summary
                    let request = Request::new(GetPortfolioSummaryRequest {
                        account_id: "mixed_test".to_string(),
                    });
                    svc.get_portfolio_summary(request).await.is_ok()
                },
            }
        });
        handles.push(handle);
    }

    // Wait for all
    let mut success_count = 0;
    for handle in handles {
        if let Ok(true) = handle.await {
            success_count += 1;
        }
    }

    println!("  ✓ {}/15 mixed endpoint requests succeeded", success_count);
    assert!(success_count >= 12, "Most mixed requests should succeed");

    Ok(())
}
