//! Comprehensive service integration tests for TLI system
//!
//! This module tests the communication between TLI client and various services
//! including Trading Service, Risk Management, Market Data, and Backtesting Service.

use fake::faker::company::en::*;
use fake::faker::finance::en::*;
use fake::{Fake, Faker};
use std::collections::HashMap;
use std::time::Duration;
use tokio::time::{sleep, timeout};
use tonic::{Request, Response, Status};
use uuid::Uuid;

use crate::integration::{TestConfig, TestUtilities};
use crate::mocks::grpc_server::{MockBacktestingServer, MockRiskServer, MockTradingServer};
use fxt::client::{
    BacktestingClient, BacktestingClientConfig, TliClientBuilder, TradingClient,
    TradingClientConfig,
};
// Database imports removed - TLI is pure client
use fxt::prelude::*;

/// Service integration test suite
pub struct ServiceIntegrationTests {
    config: TestConfig,
    trading_client: Option<TradingClient>,
    backtesting_client: Option<BacktestingClient>,
    mock_servers: Vec<Box<dyn MockServer>>,
}

/// Mock server trait for test servers
pub trait MockServer: Send + Sync {
    fn start(&mut self) -> FxtResult<u16>;
    fn stop(&mut self) -> FxtResult<()>;
    fn is_running(&self) -> bool;
    fn get_port(&self) -> Option<u16>;
}

impl ServiceIntegrationTests {
    pub fn new(config: TestConfig) -> Self {
        Self {
            config,
            trading_client: None,
            backtesting_client: None,
            mock_servers: Vec::new(),
        }
    }

    /// Setup test environment with mock services
    pub async fn setup(&mut self) -> FxtResult<()> {
        tracing::info!("Setting up service integration test environment");

        // Start mock trading service
        let mut trading_server = MockTradingServer::new(self.config.mock_server_port)?;
        let trading_port = trading_server.start()?;
        self.mock_servers.push(Box::new(trading_server));

        // Start mock backtesting service
        let mut backtesting_server =
            MockBacktestingServer::new(self.config.mock_server_port + 100)?;
        let backtesting_port = backtesting_server.start()?;
        self.mock_servers.push(Box::new(backtesting_server));

        // Wait for services to be ready
        sleep(Duration::from_millis(500)).await;

        // Create TLI client suite
        let client_suite = TliClientBuilder::new()
            .with_service_endpoint(
                "trading_service".to_string(),
                format!("http://localhost:{}", trading_port),
            )
            .with_service_endpoint(
                "backtesting_service".to_string(),
                format!("http://localhost:{}", backtesting_port),
            )
            .with_trading_config(TradingClientConfig::default())
            .with_backtesting_config(BacktestingClientConfig::default())
            .build()
            .await?;

        self.trading_client = client_suite.trading_client;
        self.backtesting_client = client_suite.backtesting_client;

        tracing::info!("Service integration test environment setup complete");
        Ok(())
    }

    /// Cleanup test environment
    pub async fn teardown(&mut self) -> FxtResult<()> {
        tracing::info!("Tearing down service integration test environment");

        // Shutdown clients
        if let Some(client) = self.trading_client.take() {
            client.shutdown().await;
        }
        if let Some(client) = self.backtesting_client.take() {
            client.shutdown().await;
        }

        // Stop mock servers
        for server in &mut self.mock_servers {
            let _ = server.stop();
        }
        self.mock_servers.clear();

        tracing::info!("Service integration test environment teardown complete");
        Ok(())
    }
}

/// Test TLI Client ↔ Trading Service communication
#[cfg(test)]
mod trading_service_tests {
    use super::*;

    #[tokio::test]
    async fn test_order_submission_flow() {
        let mut test_env = ServiceIntegrationTests::new(TestConfig::default());
        test_env
            .setup()
            .await
            .expect("Failed to setup test environment");

        let trading_client = test_env
            .trading_client
            .as_ref()
            .expect("Trading client not available");

        // Test order submission
        let order_request = SubmitOrderRequest {
            symbol: TestUtilities::generate_test_symbol("AAPL"),
            side: OrderSide::Buy as i32,
            order_type: OrderType::Market as i32,
            quantity: 100.0,
            price: Some(150.50),
            stop_price: None,
            time_in_force: "DAY".to_string(),
            client_order_id: Uuid::new_v4().to_string(),
        };

        let result = timeout(
            Duration::from_secs(10),
            trading_client.submit_order(order_request),
        )
        .await;

        assert!(result.is_ok(), "Order submission timed out");
        let response = result.unwrap().expect("Order submission failed");
        assert!(response.success, "Order submission was not successful");
        assert!(
            !response.order_id.is_empty(),
            "Order ID should not be empty"
        );

        test_env
            .teardown()
            .await
            .expect("Failed to teardown test environment");
    }

    #[tokio::test]
    async fn test_order_cancellation_flow() {
        let mut test_env = ServiceIntegrationTests::new(TestConfig::default());
        test_env
            .setup()
            .await
            .expect("Failed to setup test environment");

        let trading_client = test_env
            .trading_client
            .as_ref()
            .expect("Trading client not available");

        // First submit an order
        let order_request = SubmitOrderRequest {
            symbol: TestUtilities::generate_test_symbol("GOOGL"),
            side: OrderSide::Buy as i32,
            order_type: OrderType::Limit as i32,
            quantity: 50.0,
            price: Some(2800.00),
            stop_price: None,
            time_in_force: "GTC".to_string(),
            client_order_id: Uuid::new_v4().to_string(),
        };

        let submit_response = trading_client
            .submit_order(order_request)
            .await
            .expect("Failed to submit order");
        assert!(submit_response.success, "Order submission failed");

        // Then cancel the order
        let cancel_request = CancelOrderRequest {
            order_id: submit_response.order_id.clone(),
            symbol: TestUtilities::generate_test_symbol("GOOGL"),
        };

        let cancel_response = trading_client
            .cancel_order(cancel_request)
            .await
            .expect("Failed to cancel order");
        assert!(cancel_response.success, "Order cancellation failed");

        test_env
            .teardown()
            .await
            .expect("Failed to teardown test environment");
    }

    #[tokio::test]
    async fn test_position_monitoring() {
        let mut test_env = ServiceIntegrationTests::new(TestConfig::default());
        test_env
            .setup()
            .await
            .expect("Failed to setup test environment");

        let trading_client = test_env
            .trading_client
            .as_ref()
            .expect("Trading client not available");

        // Test position query
        let position_request = GetPositionsRequest {
            account_id: Some("test_account".to_string()),
            symbol_filter: None,
            include_zero_positions: false,
        };

        let positions_response = trading_client
            .get_positions(position_request)
            .await
            .expect("Failed to get positions");

        // Validate response structure
        assert!(
            !positions_response.positions.is_empty(),
            "Should have at least mock positions"
        );

        for position in &positions_response.positions {
            assert!(
                !position.symbol.is_empty(),
                "Position symbol should not be empty"
            );
            assert!(
                position.quantity != 0.0,
                "Position quantity should not be zero"
            );
        }

        test_env
            .teardown()
            .await
            .expect("Failed to teardown test environment");
    }

    #[tokio::test]
    async fn test_market_data_subscription() {
        let mut test_env = ServiceIntegrationTests::new(TestConfig::default());
        test_env
            .setup()
            .await
            .expect("Failed to setup test environment");

        let trading_client = test_env
            .trading_client
            .as_ref()
            .expect("Trading client not available");

        // Test market data subscription
        let subscription_request = MarketDataSubscriptionRequest {
            symbols: vec!["AAPL".to_string(), "GOOGL".to_string(), "MSFT".to_string()],
            data_types: vec![MarketDataType::Quote as i32, MarketDataType::Trade as i32],
            include_level2: false,
        };

        let mut stream = trading_client
            .subscribe_market_data(subscription_request)
            .await
            .expect("Failed to subscribe to market data");

        // Collect a few market data updates
        let mut updates_received = 0;
        let timeout_duration = Duration::from_secs(5);
        let start_time = std::time::Instant::now();

        while updates_received < 3 && start_time.elapsed() < timeout_duration {
            match timeout(Duration::from_millis(500), stream.recv()).await {
                Ok(Some(update)) => {
                    assert!(
                        !update.symbol.is_empty(),
                        "Market data symbol should not be empty"
                    );
                    assert!(
                        update.timestamp > 0,
                        "Market data timestamp should be valid"
                    );
                    updates_received += 1;
                    tracing::info!("Received market data update for {}", update.symbol);
                }
                Ok(None) => break,
                Err(_) => {
                    tracing::warn!("Market data update timed out");
                    break;
                }
            }
        }

        assert!(
            updates_received > 0,
            "Should have received at least one market data update"
        );

        test_env
            .teardown()
            .await
            .expect("Failed to teardown test environment");
    }

    #[tokio::test]
    async fn test_risk_management_validation() {
        let mut test_env = ServiceIntegrationTests::new(TestConfig::default());
        test_env
            .setup()
            .await
            .expect("Failed to setup test environment");

        let trading_client = test_env
            .trading_client
            .as_ref()
            .expect("Trading client not available");

        // Test pre-trade risk validation
        let risk_check_request = PreTradeRiskCheckRequest {
            symbol: TestUtilities::generate_test_symbol("TSLA"),
            side: OrderSide::Buy as i32,
            quantity: 1000.0, // Large quantity to trigger risk limits
            estimated_price: Some(800.0),
            order_type: OrderType::Market as i32,
            account_id: "test_account".to_string(),
        };

        let risk_response = trading_client
            .check_pre_trade_risk(risk_check_request)
            .await
            .expect("Failed to perform risk check");

        // Validate risk check response
        assert!(
            risk_response.risk_score >= 0.0 && risk_response.risk_score <= 1.0,
            "Risk score should be between 0 and 1"
        );

        if !risk_response.approved {
            assert!(
                !risk_response.rejection_reasons.is_empty(),
                "Rejection reasons should be provided when order is not approved"
            );
        }

        test_env
            .teardown()
            .await
            .expect("Failed to teardown test environment");
    }

    #[tokio::test]
    async fn test_portfolio_analytics() {
        let mut test_env = ServiceIntegrationTests::new(TestConfig::default());
        test_env
            .setup()
            .await
            .expect("Failed to setup test environment");

        let trading_client = test_env
            .trading_client
            .as_ref()
            .expect("Trading client not available");

        // Test portfolio analytics
        let analytics_request = PortfolioAnalyticsRequest {
            account_id: "test_account".to_string(),
            calculation_date: chrono::Utc::now().timestamp(),
            include_realized_pnl: true,
            include_unrealized_pnl: true,
            include_risk_metrics: true,
        };

        let analytics_response = trading_client
            .get_portfolio_analytics(analytics_request)
            .await
            .expect("Failed to get portfolio analytics");

        // Validate analytics response
        assert!(
            analytics_response.total_portfolio_value.is_some(),
            "Total portfolio value should be provided"
        );
        assert!(
            analytics_response.daily_pnl.is_some(),
            "Daily P&L should be provided"
        );
        assert!(
            !analytics_response.position_analytics.is_empty(),
            "Position analytics should not be empty"
        );

        test_env
            .teardown()
            .await
            .expect("Failed to teardown test environment");
    }
}

/// Test TLI Client ↔ Backtesting Service communication
#[cfg(test)]
mod backtesting_service_tests {
    use super::*;

    #[tokio::test]
    async fn test_backtest_execution_flow() {
        let mut test_env = ServiceIntegrationTests::new(TestConfig::default());
        test_env
            .setup()
            .await
            .expect("Failed to setup test environment");

        let backtesting_client = test_env
            .backtesting_client
            .as_ref()
            .expect("Backtesting client not available");

        // Create backtest configuration
        let backtest_request = CreateBacktestRequest {
            name: format!("Integration Test Backtest {}", Uuid::new_v4()),
            strategy_id: "test_strategy".to_string(),
            start_date: "2024-01-01".to_string(),
            end_date: "2024-01-31".to_string(),
            initial_capital: 100000.0,
            symbols: vec!["AAPL".to_string(), "GOOGL".to_string()],
            parameters: HashMap::new(),
        };

        // Start backtest
        let create_response = backtesting_client
            .create_backtest(backtest_request)
            .await
            .expect("Failed to create backtest");
        assert!(create_response.success, "Backtest creation failed");
        assert!(
            !create_response.backtest_id.is_empty(),
            "Backtest ID should not be empty"
        );

        let backtest_id = create_response.backtest_id;

        // Start backtest execution
        let start_request = StartBacktestRequest {
            backtest_id: backtest_id.clone(),
            async_execution: true,
        };

        let start_response = backtesting_client
            .start_backtest(start_request)
            .await
            .expect("Failed to start backtest");
        assert!(start_response.success, "Backtest start failed");

        // Monitor backtest progress
        let mut progress_checks = 0;
        let max_progress_checks = 10;

        while progress_checks < max_progress_checks {
            let status_request = GetBacktestStatusRequest {
                backtest_id: backtest_id.clone(),
            };

            let status_response = backtesting_client
                .get_backtest_status(status_request)
                .await
                .expect("Failed to get backtest status");

            tracing::info!(
                "Backtest status: {:?}, Progress: {}%",
                status_response.status,
                status_response.progress_percentage
            );

            if status_response.status == BacktestStatus::Completed as i32 {
                break;
            }

            progress_checks += 1;
            sleep(Duration::from_millis(100)).await;
        }

        // Get backtest results
        let results_request = GetBacktestResultsRequest {
            backtest_id: backtest_id.clone(),
            include_trades: true,
            include_metrics: true,
        };

        let results_response = backtesting_client
            .get_backtest_results(results_request)
            .await
            .expect("Failed to get backtest results");

        // Validate results
        assert!(
            results_response.performance_summary.is_some(),
            "Performance summary should be provided"
        );
        assert!(
            !results_response.trades.is_empty(),
            "Should have executed some trades"
        );

        test_env
            .teardown()
            .await
            .expect("Failed to teardown test environment");
    }

    #[tokio::test]
    async fn test_strategy_optimization() {
        let mut test_env = ServiceIntegrationTests::new(TestConfig::default());
        test_env
            .setup()
            .await
            .expect("Failed to setup test environment");

        let backtesting_client = test_env
            .backtesting_client
            .as_ref()
            .expect("Backtesting client not available");

        // Create optimization request
        let optimization_request = StrategyOptimizationRequest {
            strategy_id: "test_strategy".to_string(),
            start_date: "2024-01-01".to_string(),
            end_date: "2024-03-31".to_string(),
            symbols: vec!["AAPL".to_string()],
            parameter_ranges: create_test_parameter_ranges(),
            optimization_metric: OptimizationMetric::SharpeRatio as i32,
            max_iterations: 50,
        };

        // Start optimization
        let optimization_response = backtesting_client
            .optimize_strategy(optimization_request)
            .await
            .expect("Failed to start strategy optimization");

        assert!(
            optimization_response.success,
            "Strategy optimization start failed"
        );
        assert!(
            !optimization_response.optimization_id.is_empty(),
            "Optimization ID should not be empty"
        );

        test_env
            .teardown()
            .await
            .expect("Failed to teardown test environment");
    }

    #[tokio::test]
    async fn test_backtest_data_management() {
        let mut test_env = ServiceIntegrationTests::new(TestConfig::default());
        test_env
            .setup()
            .await
            .expect("Failed to setup test environment");

        let backtesting_client = test_env
            .backtesting_client
            .as_ref()
            .expect("Backtesting client not available");

        // Test data availability check
        let data_request = CheckDataAvailabilityRequest {
            symbols: vec!["AAPL".to_string(), "GOOGL".to_string(), "MSFT".to_string()],
            start_date: "2024-01-01".to_string(),
            end_date: "2024-01-31".to_string(),
            data_types: vec![DataType::OHLCV as i32, DataType::Trades as i32],
        };

        let data_response = backtesting_client
            .check_data_availability(data_request)
            .await
            .expect("Failed to check data availability");

        // Validate data availability response
        assert!(
            !data_response.symbol_availability.is_empty(),
            "Symbol availability should not be empty"
        );

        for (symbol, availability) in &data_response.symbol_availability {
            assert!(!symbol.is_empty(), "Symbol should not be empty");
            assert!(
                availability.coverage_percentage >= 0.0
                    && availability.coverage_percentage <= 100.0,
                "Coverage percentage should be between 0 and 100"
            );
        }

        test_env
            .teardown()
            .await
            .expect("Failed to teardown test environment");
    }

    fn create_test_parameter_ranges() -> HashMap<String, ParameterRange> {
        let mut ranges = HashMap::new();

        ranges.insert(
            "lookback_period".to_string(),
            ParameterRange {
                min_value: 5.0,
                max_value: 50.0,
                step_size: 5.0,
                parameter_type: ParameterType::Integer as i32,
            },
        );

        ranges.insert(
            "threshold".to_string(),
            ParameterRange {
                min_value: 0.01,
                max_value: 0.1,
                step_size: 0.01,
                parameter_type: ParameterType::Float as i32,
            },
        );

        ranges
    }
}

/// Configuration management integration tests
#[cfg(test)]
mod config_integration_tests {
    use super::*;

    #[tokio::test]
    async fn test_configuration_lifecycle() {
        let mut test_env = ServiceIntegrationTests::new(TestConfig::default());
        test_env
            .setup()
            .await
            .expect("Failed to setup test environment");

        let trading_client = test_env
            .trading_client
            .as_ref()
            .expect("Trading client not available");

        // Test configuration retrieval
        let get_config_request = GetConfigurationRequest {
            config_type: ConfigurationType::TradingLimits as i32,
            account_id: Some("test_account".to_string()),
        };

        let config_response = trading_client
            .get_configuration(get_config_request)
            .await
            .expect("Failed to get configuration");

        assert!(
            !config_response.configurations.is_empty(),
            "Should have configurations"
        );

        // Test configuration update
        let updated_config = Configuration {
            id: config_response.configurations[0].id.clone(),
            config_type: ConfigurationType::TradingLimits as i32,
            key: "max_position_size".to_string(),
            value: "1000000".to_string(),
            account_id: Some("test_account".to_string()),
            last_updated: chrono::Utc::now().timestamp(),
        };

        let update_request = UpdateConfigurationRequest {
            configurations: vec![updated_config],
            validate_before_update: true,
        };

        let update_response = trading_client
            .update_configuration(update_request)
            .await
            .expect("Failed to update configuration");

        assert!(
            update_response.success,
            "Configuration update should succeed"
        );
        assert!(
            update_response.validation_errors.is_empty(),
            "Should have no validation errors"
        );

        test_env
            .teardown()
            .await
            .expect("Failed to teardown test environment");
    }

    #[tokio::test]
    async fn test_hot_reload_configuration() {
        let mut test_env = ServiceIntegrationTests::new(TestConfig::default());
        test_env
            .setup()
            .await
            .expect("Failed to setup test environment");

        let trading_client = test_env
            .trading_client
            .as_ref()
            .expect("Trading client not available");

        // Test hot reload trigger
        let reload_request = TriggerConfigReloadRequest {
            services: vec!["trading_service".to_string(), "risk_service".to_string()],
            config_types: vec![ConfigurationType::TradingLimits as i32],
        };

        let reload_response = trading_client
            .trigger_config_reload(reload_request)
            .await
            .expect("Failed to trigger config reload");

        assert!(reload_response.success, "Config reload should succeed");

        for result in &reload_response.service_results {
            assert!(result.success, "Each service reload should succeed");
        }

        test_env
            .teardown()
            .await
            .expect("Failed to teardown test environment");
    }
}

/// Event streaming integration tests
#[cfg(test)]
mod event_streaming_tests {
    use super::*;

    #[tokio::test]
    async fn test_event_streaming_lifecycle() {
        let mut test_env = ServiceIntegrationTests::new(TestConfig::default());
        test_env
            .setup()
            .await
            .expect("Failed to setup test environment");

        let trading_client = test_env
            .trading_client
            .as_ref()
            .expect("Trading client not available");

        // Subscribe to events
        let subscription_request = EventSubscriptionRequest {
            event_types: vec![
                EventType::OrderExecuted as i32,
                EventType::PositionChanged as i32,
                EventType::RiskLimitBreached as i32,
            ],
            filters: HashMap::new(),
        };

        let mut event_stream = trading_client
            .subscribe_to_events(subscription_request)
            .await
            .expect("Failed to subscribe to events");

        // Generate some events by placing orders
        let order_request = SubmitOrderRequest {
            symbol: TestUtilities::generate_test_symbol("AAPL"),
            side: OrderSide::Buy as i32,
            order_type: OrderType::Market as i32,
            quantity: 100.0,
            price: None,
            stop_price: None,
            time_in_force: "DAY".to_string(),
            client_order_id: Uuid::new_v4().to_string(),
        };

        // Submit order to generate events
        let _order_response = trading_client
            .submit_order(order_request)
            .await
            .expect("Failed to submit order");

        // Collect events
        let mut events_received = 0;
        let timeout_duration = Duration::from_secs(5);
        let start_time = std::time::Instant::now();

        while events_received < 2 && start_time.elapsed() < timeout_duration {
            match timeout(Duration::from_millis(500), event_stream.recv()).await {
                Ok(Some(event)) => {
                    assert!(!event.event_id.is_empty(), "Event ID should not be empty");
                    assert!(event.timestamp > 0, "Event timestamp should be valid");
                    events_received += 1;
                    tracing::info!("Received event: {:?}", event.event_type);
                }
                Ok(None) => break,
                Err(_) => {
                    tracing::warn!("Event stream timed out");
                    break;
                }
            }
        }

        assert!(
            events_received > 0,
            "Should have received at least one event"
        );

        test_env
            .teardown()
            .await
            .expect("Failed to teardown test environment");
    }
}
