//! Position Lifecycle Integration Tests
//!
//! Comprehensive tests for position management covering:
//! - Position opening and closing
//! - Real-time PnL calculation and tracking
//! - Position updates from order fills
//! - Multi-symbol position management
//! - Position reconciliation
//! - Edge cases: zero positions, negative positions (shorts)

use anyhow::Result;
use std::sync::Arc;
use tonic::Request;
use trading_service::proto::trading::{
    trading_service_server::TradingService, GetPortfolioSummaryRequest, GetPositionsRequest,
    OrderSide, OrderType, 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))
}

// ============================================================================
// Position Opening Tests
// ============================================================================

#[tokio::test]
async fn test_open_long_position() -> Result<()> {
    println!("\n=== Test: Open Long Position ===");

    let service = setup_trading_service().await?;
    let account_id = "position_test_001";

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

    let buy_request = Request::new(SubmitOrderRequest {
        account_id: account_id.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 buy_response = service.submit_order(buy_request).await?;
    let order_id = buy_response.into_inner().order_id;
    println!("  Buy order submitted: {}", order_id);

    // Get positions
    let positions_request = Request::new(GetPositionsRequest {
        account_id: Some(account_id.to_string()),
        symbol: Some("AAPL".to_string()),
    });

    let positions_response = service.get_positions(positions_request).await?;
    let positions = positions_response.into_inner();

    println!("  Positions found: {}", positions.positions.len());
    for pos in &positions.positions {
        println!("    Symbol: {}, Quantity: {}", pos.symbol, pos.quantity);
    }

    Ok(())
}

#[tokio::test]
async fn test_open_short_position() -> Result<()> {
    println!("\n=== Test: Open Short Position ===");

    let service = setup_trading_service().await?;
    let account_id = "position_test_002";

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

    let sell_request = Request::new(SubmitOrderRequest {
        account_id: account_id.to_string(),
        symbol: "TSLA".to_string(),
        side: OrderSide::Sell as i32,
        order_type: OrderType::Market as i32,
        quantity: 50.0,
        price: None,
        stop_price: None,
        metadata,
    });

    let sell_response = service.submit_order(sell_request).await?;
    let order_id = sell_response.into_inner().order_id;
    println!("  Sell order submitted: {}", order_id);

    // Get positions
    let positions_request = Request::new(GetPositionsRequest {
        account_id: Some(account_id.to_string()),
        symbol: Some("TSLA".to_string()),
    });

    let positions_response = service.get_positions(positions_request).await?;
    let positions = positions_response.into_inner();

    println!("  Positions found: {}", positions.positions.len());
    for pos in &positions.positions {
        println!("    Symbol: {}, Quantity: {}", pos.symbol, pos.quantity);
        // Short position should show negative quantity
    }

    Ok(())
}

#[tokio::test]
async fn test_open_multiple_positions() -> Result<()> {
    println!("\n=== Test: Open Multiple Positions ===");

    let service = setup_trading_service().await?;
    let account_id = "position_test_003";

    let symbols = vec![
        ("AAPL", 100.0),
        ("GOOGL", 50.0),
        ("MSFT", 75.0),
        ("NVDA", 25.0),
    ];

    // Open positions in multiple symbols
    for (symbol, quantity) in &symbols {
        let mut metadata = std::collections::HashMap::new();
        metadata.insert("time_in_force".to_string(), "GTC".to_string());

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

        let response = service.submit_order(request).await?;
        println!("  Position opened: {} - {} shares", symbol, quantity);
        let _ = response.into_inner();
    }

    // Get all positions
    let positions_request = Request::new(GetPositionsRequest {
        account_id: Some(account_id.to_string()),
        symbol: None, // All symbols
    });

    let positions_response = service.get_positions(positions_request).await?;
    let positions = positions_response.into_inner();

    println!("  Total positions: {}", positions.positions.len());
    for pos in &positions.positions {
        println!("    {}: {} shares", pos.symbol, pos.quantity);
    }

    Ok(())
}

// ============================================================================
// Position Closing Tests
// ============================================================================

#[tokio::test]
async fn test_close_long_position() -> Result<()> {
    println!("\n=== Test: Close Long Position ===");

    let service = setup_trading_service().await?;
    let account_id = "position_test_004";

    // 1. Open long position
    let mut metadata = std::collections::HashMap::new();
    metadata.insert("time_in_force".to_string(), "GTC".to_string());

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

    let _ = service.submit_order(buy_request).await?;
    println!("  1. Long position opened: 100 AMD");

    // 2. Close position
    let sell_request = Request::new(SubmitOrderRequest {
        account_id: account_id.to_string(),
        symbol: "AMD".to_string(),
        side: OrderSide::Sell as i32,
        order_type: OrderType::Market as i32,
        quantity: 100.0,
        price: None,
        stop_price: None,
        metadata,
    });

    let _ = service.submit_order(sell_request).await?;
    println!("  2. Position closed: Sold 100 AMD");

    // 3. Verify position is zero or removed
    let positions_request = Request::new(GetPositionsRequest {
        account_id: Some(account_id.to_string()),
        symbol: Some("AMD".to_string()),
    });

    let positions_response = service.get_positions(positions_request).await?;
    let positions = positions_response.into_inner();

    println!("  3. Positions after close: {}", positions.positions.len());
    for pos in &positions.positions {
        println!("    {}: {} shares", pos.symbol, pos.quantity);
    }

    Ok(())
}

#[tokio::test]
async fn test_partial_position_close() -> Result<()> {
    println!("\n=== Test: Partial Position Close ===");

    let service = setup_trading_service().await?;
    let account_id = "position_test_005";

    // 1. Open position with 200 shares
    let mut metadata = std::collections::HashMap::new();
    metadata.insert("time_in_force".to_string(), "GTC".to_string());

    let buy_request = Request::new(SubmitOrderRequest {
        account_id: account_id.to_string(),
        symbol: "SPY".to_string(),
        side: OrderSide::Buy as i32,
        order_type: OrderType::Market as i32,
        quantity: 200.0,
        price: None,
        stop_price: None,
        metadata: metadata.clone(),
    });

    let _ = service.submit_order(buy_request).await?;
    println!("  1. Position opened: 200 SPY");

    // 2. Partially close (sell 75 shares)
    let sell_request = Request::new(SubmitOrderRequest {
        account_id: account_id.to_string(),
        symbol: "SPY".to_string(),
        side: OrderSide::Sell as i32,
        order_type: OrderType::Market as i32,
        quantity: 75.0,
        price: None,
        stop_price: None,
        metadata,
    });

    let _ = service.submit_order(sell_request).await?;
    println!("  2. Partially closed: Sold 75 SPY");

    // 3. Check remaining position (should be ~125 shares)
    let positions_request = Request::new(GetPositionsRequest {
        account_id: Some(account_id.to_string()),
        symbol: Some("SPY".to_string()),
    });

    let positions_response = service.get_positions(positions_request).await?;
    let positions = positions_response.into_inner();

    println!("  3. Remaining position:");
    for pos in &positions.positions {
        println!("    {}: {} shares", pos.symbol, pos.quantity);
    }

    Ok(())
}

// ============================================================================
// PnL Tracking Tests
// ============================================================================

#[tokio::test]
async fn test_portfolio_summary() -> Result<()> {
    println!("\n=== Test: Portfolio Summary ===");

    let service = setup_trading_service().await?;
    let account_id = "position_test_006";

    // Open some positions
    let symbols = vec![("AAPL", 50.0), ("GOOGL", 25.0)];

    for (symbol, quantity) in &symbols {
        let mut metadata = std::collections::HashMap::new();
        metadata.insert("time_in_force".to_string(), "GTC".to_string());

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

        let _ = service.submit_order(request).await?;
    }

    // Get portfolio summary
    let summary_request = Request::new(GetPortfolioSummaryRequest {
        account_id: account_id.to_string(),
    });

    let summary_response = service.get_portfolio_summary(summary_request).await?;
    let summary = summary_response.into_inner();

    println!("\n  Portfolio Summary:");
    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_unrealized_pnl_calculation() -> Result<()> {
    println!("\n=== Test: Unrealized PnL Calculation ===");

    let service = setup_trading_service().await?;
    let account_id = "position_test_007";

    // Open position
    let mut metadata = std::collections::HashMap::new();
    metadata.insert("time_in_force".to_string(), "GTC".to_string());
    metadata.insert("entry_price".to_string(), "100.00".to_string());

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

    let _ = service.submit_order(request).await?;
    println!("  Position opened at $100.00");

    // Get position and check unrealized PnL
    let positions_request = Request::new(GetPositionsRequest {
        account_id: Some(account_id.to_string()),
        symbol: Some("AAPL".to_string()),
    });

    let positions_response = service.get_positions(positions_request).await?;
    let positions = positions_response.into_inner();

    for pos in &positions.positions {
        println!("\n  Position Details:");
        println!("  ├─ Symbol: {}", pos.symbol);
        println!("  ├─ Quantity: {}", pos.quantity);
        println!("  ├─ Average Price: ${:.2}", pos.average_price);
        // Note: current_price not in proto, using market_value/quantity
        let current_price = if pos.quantity != 0.0 {
            pos.market_value / pos.quantity.abs()
        } else {
            0.0
        };
        println!("  ├─ Current Price: ${:.2}", current_price);
        println!("  ├─ Unrealized PnL: ${:.2}", pos.unrealized_pnl);
        println!("  └─ Total Value: ${:.2}", pos.market_value);
    }

    Ok(())
}

#[tokio::test]
async fn test_realized_pnl_on_close() -> Result<()> {
    println!("\n=== Test: Realized PnL on Close ===");

    let service = setup_trading_service().await?;
    let account_id = "position_test_008";

    // 1. Buy at specific price
    let mut buy_metadata = std::collections::HashMap::new();
    buy_metadata.insert("time_in_force".to_string(), "GTC".to_string());

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

    let _ = service.submit_order(buy_request).await?;
    println!("  1. Bought 50 MSFT at $350.00");

    // 2. Sell at different price
    let mut sell_metadata = std::collections::HashMap::new();
    sell_metadata.insert("time_in_force".to_string(), "GTC".to_string());

    let sell_request = Request::new(SubmitOrderRequest {
        account_id: account_id.to_string(),
        symbol: "MSFT".to_string(),
        side: OrderSide::Sell as i32,
        order_type: OrderType::Limit as i32,
        quantity: 50.0,
        price: Some(360.00),
        stop_price: None,
        metadata: sell_metadata,
    });

    let _ = service.submit_order(sell_request).await?;
    println!("  2. Sold 50 MSFT at $360.00");

    // 3. Check portfolio summary for realized PnL
    let summary_request = Request::new(GetPortfolioSummaryRequest {
        account_id: account_id.to_string(),
    });

    let summary_response = service.get_portfolio_summary(summary_request).await?;
    let summary = summary_response.into_inner();

    println!("\n  Realized PnL: ${:.2}", summary.realized_pnl);
    println!("  Expected: $500.00 (50 shares × $10 profit)");

    Ok(())
}

// ============================================================================
// Position Reconciliation Tests
// ============================================================================

#[tokio::test]
async fn test_position_average_price_calculation() -> Result<()> {
    println!("\n=== Test: Position Average Price Calculation ===");

    let service = setup_trading_service().await?;
    let account_id = "position_test_009";

    // Buy in multiple lots at different prices
    let lots = vec![(50.0, 100.00), (30.0, 105.00), (20.0, 98.00)];

    for (quantity, price) in &lots {
        let mut metadata = std::collections::HashMap::new();
        metadata.insert("time_in_force".to_string(), "GTC".to_string());

        let request = Request::new(SubmitOrderRequest {
            account_id: account_id.to_string(),
            symbol: "NVDA".to_string(),
            side: OrderSide::Buy as i32,
            order_type: OrderType::Limit as i32,
            quantity: *quantity,
            price: Some(*price),
            stop_price: None,
            metadata,
        });

        let _ = service.submit_order(request).await?;
        println!("  Bought {} NVDA at ${:.2}", quantity, price);
    }

    // Check average entry price
    let positions_request = Request::new(GetPositionsRequest {
        account_id: Some(account_id.to_string()),
        symbol: Some("NVDA".to_string()),
    });

    let positions_response = service.get_positions(positions_request).await?;
    let positions = positions_response.into_inner();

    for pos in &positions.positions {
        let expected_avg = (50.0 * 100.00 + 30.0 * 105.00 + 20.0 * 98.00) / 100.0;
        println!("\n  Total Position: {} shares", pos.quantity);
        println!("  Average Entry Price: ${:.2}", pos.average_price);
        println!("  Expected: ${:.2}", expected_avg);
    }

    Ok(())
}

#[tokio::test]
async fn test_zero_position_after_equal_buys_sells() -> Result<()> {
    println!("\n=== Test: Zero Position After Equal Buys/Sells ===");

    let service = setup_trading_service().await?;
    let account_id = "position_test_010";

    let quantity = 100.0;

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

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

    let _ = service.submit_order(buy_request).await?;
    println!("  Bought {} AMD", quantity);

    // Sell equal amount
    let sell_request = Request::new(SubmitOrderRequest {
        account_id: account_id.to_string(),
        symbol: "AMD".to_string(),
        side: OrderSide::Sell as i32,
        order_type: OrderType::Market as i32,
        quantity,
        price: None,
        stop_price: None,
        metadata,
    });

    let _ = service.submit_order(sell_request).await?;
    println!("  Sold {} AMD", quantity);

    // Position should be zero
    let positions_request = Request::new(GetPositionsRequest {
        account_id: Some(account_id.to_string()),
        symbol: Some("AMD".to_string()),
    });

    let positions_response = service.get_positions(positions_request).await?;
    let positions = positions_response.into_inner();

    println!("\n  Positions: {}", positions.positions.len());
    for pos in &positions.positions {
        println!(
            "    {}: {} shares (should be 0 or absent)",
            pos.symbol, pos.quantity
        );
    }

    Ok(())
}

// ============================================================================
// Edge Cases and Error Handling
// ============================================================================

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

    let service = setup_trading_service().await?;

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

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

    println!(
        "  Positions for empty account: {}",
        positions.positions.len()
    );
    assert_eq!(positions.positions.len(), 0);

    Ok(())
}

#[tokio::test]
async fn test_get_positions_nonexistent_symbol() -> Result<()> {
    println!("\n=== Test: Get Positions - Nonexistent Symbol ===");

    let service = setup_trading_service().await?;
    let account_id = "position_test_011";

    // Open position in AAPL
    let mut metadata = std::collections::HashMap::new();
    metadata.insert("time_in_force".to_string(), "GTC".to_string());

    let request = Request::new(SubmitOrderRequest {
        account_id: account_id.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,
    });

    let _ = service.submit_order(request).await?;

    // Query for different symbol
    let positions_request = Request::new(GetPositionsRequest {
        account_id: Some(account_id.to_string()),
        symbol: Some("NONEXISTENT".to_string()),
    });

    let positions_response = service.get_positions(positions_request).await?;
    let positions = positions_response.into_inner();

    println!(
        "  Positions for nonexistent symbol: {}",
        positions.positions.len()
    );
    assert_eq!(positions.positions.len(), 0);

    Ok(())
}

#[tokio::test]
async fn test_position_with_fractional_shares() -> Result<()> {
    println!("\n=== Test: Position with Fractional Shares ===");

    let service = setup_trading_service().await?;
    let account_id = "position_test_012";

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

    let request = Request::new(SubmitOrderRequest {
        account_id: account_id.to_string(),
        symbol: "TSLA".to_string(),
        side: OrderSide::Buy as i32,
        order_type: OrderType::Market as i32,
        quantity: 15.75, // Fractional shares
        price: None,
        stop_price: None,
        metadata,
    });

    let response = service.submit_order(request).await?;
    println!("  Fractional order: {}", response.into_inner().order_id);

    let positions_request = Request::new(GetPositionsRequest {
        account_id: Some(account_id.to_string()),
        symbol: Some("TSLA".to_string()),
    });

    let positions_response = service.get_positions(positions_request).await?;
    let positions = positions_response.into_inner();

    for pos in &positions.positions {
        println!("  Position: {} shares of {}", pos.quantity, pos.symbol);
    }

    Ok(())
}
