//! Predefined Test Scenarios for Foxhunt HFT Trading System
//!
//! This module provides comprehensive test scenarios for various trading,
//! risk management, and market conditions that the system needs to handle.
//!
//! ## Usage
//!
//! ```rust
//! use tests::fixtures::scenarios::*;
//!
//! // Get a basic trading scenario
//! let scenario = BasicTradingScenario::new();
//! let positions = scenario.create_positions();
//!
//! // Get a stress test scenario
//! let stress_scenario = MarketCrashScenario::new();
//! let shocks = stress_scenario.generate_market_shocks();
//!
//! // Get a high frequency scenario
//! let hft_scenario = HighFrequencyScenario::new();
//! let orders = hft_scenario.generate_order_flow(1000);
//! ```

use chrono::{DateTime, Duration as ChronoDuration, Utc};
use rust_decimal::prelude::ToPrimitive;
use rust_decimal::Decimal;
use std::collections::HashMap;
use uuid::Uuid;

// Import types from risk crate
use risk::risk_types::Position;
// Note: StressScenario imported from mod.rs (risk_data::models version)
use super::builders::*;
use super::*;
use crate::fixtures::helpers::ToDecimal;

// =============================================================================
// BASIC TRADING SCENARIOS
// =============================================================================

/// Basic trading scenario with mixed positions
#[derive(Debug, Clone)]
pub struct BasicTradingScenario {
    pub portfolio_id: String,
    pub base_currency: String,
    pub total_value: Decimal,
}

impl Default for BasicTradingScenario {
    fn default() -> Self {
        Self::new()
    }
}

impl BasicTradingScenario {
    pub fn new() -> Self {
        Self {
            portfolio_id: TEST_PORTFOLIO_1.to_string(),
            base_currency: "USD".to_string(),
            total_value: Decimal::from(1000000), // $1M portfolio
        }
    }

    pub fn with_portfolio_id(mut self, portfolio_id: impl Into<String>) -> Self {
        self.portfolio_id = portfolio_id.into();
        self
    }

    pub fn with_total_value(mut self, value: Decimal) -> Self {
        self.total_value = value;
        self
    }

    /// Create a portfolio with the scenario settings
    pub fn create_portfolio(&self) -> Portfolio {
        PortfolioBuilder::new()
            .with_id(&self.portfolio_id)
            .with_name("Basic Trading Portfolio")
            .with_base_currency(&self.base_currency)
            .strategy_portfolio()
            .build()
    }

    /// Create diverse positions across asset classes
    pub fn create_positions(&self) -> Vec<Position> {
        let symbols_and_weights = vec![
            (TEST_EQUITY_1, 0.30),    // 30% large cap equity
            (TEST_EQUITY_2, 0.20),    // 20% mid cap equity
            (TEST_FOREX_1, 0.15),     // 15% major FX pair
            (TEST_FUTURE_1, 0.10),    // 10% equity futures
            (TEST_BOND_1, 0.15),      // 15% government bonds
            (TEST_COMMODITY_1, 0.10), // 10% gold commodity
        ];

        symbols_and_weights
            .into_iter()
            .map(|(symbol, weight)| {
                let position_value = self.total_value * Decimal::try_from(weight).unwrap();
                let price = get_test_price_for_symbol(symbol).to_decimal();
                let quantity = position_value / price;

                PositionBuilder::new()
                    .with_portfolio_id(&self.portfolio_id)
                    .with_symbol(symbol)
                    .with_quantity(quantity)
                    .with_average_price(price)
                    .with_market_price(price)
                    .with_weight(Decimal::try_from(weight).unwrap())
                    .build()
            })
            .collect()
    }

    /// Create corresponding instruments for all positions
    pub fn create_instruments(&self) -> Vec<Instrument> {
        vec![
            InstrumentBuilder::new()
                .with_symbol(TEST_EQUITY_1)
                .equity()
                .build(),
            InstrumentBuilder::new()
                .with_symbol(TEST_EQUITY_2)
                .equity()
                .build(),
            InstrumentBuilder::new()
                .with_symbol(TEST_FOREX_1)
                .currency()
                .build(),
            InstrumentBuilder::new()
                .with_symbol(TEST_FUTURE_1)
                .future()
                .build(),
            InstrumentBuilder::new()
                .with_symbol(TEST_BOND_1)
                .bond()
                .build(),
            InstrumentBuilder::new()
                .with_symbol(TEST_COMMODITY_1)
                .commodity()
                .build(),
        ]
    }
}

// =============================================================================
// STRESS TEST SCENARIOS
// =============================================================================

/// Market crash stress test scenario
#[derive(Debug, Clone)]
pub struct MarketCrashScenario {
    pub name: String,
    pub description: String,
    pub equity_shock: Decimal,     // -30%
    pub bond_shock: Decimal,       // +5% (flight to quality)
    pub commodity_shock: Decimal,  // -20%
    pub fx_shock: Decimal,         // +10% USD strength
    pub volatility_shock: Decimal, // +200% volatility increase
}

impl Default for MarketCrashScenario {
    fn default() -> Self {
        Self::new()
    }
}

impl MarketCrashScenario {
    pub fn new() -> Self {
        Self {
            name: "Market Crash 2008 Style".to_string(),
            description: "Severe market downturn with flight to quality".to_string(),
            equity_shock: Decimal::new(-30, 2),     // -30%
            bond_shock: Decimal::new(5, 2),         // +5%
            commodity_shock: Decimal::new(-20, 2),  // -20%
            fx_shock: Decimal::new(10, 2),          // +10%
            volatility_shock: Decimal::new(200, 2), // +200%
        }
    }

    /// Generate market shocks for all asset classes
    pub fn generate_market_shocks(&self) -> HashMap<AssetClass, Decimal> {
        let mut shocks = HashMap::new();
        shocks.insert(AssetClass::Equities, self.equity_shock);
        shocks.insert(AssetClass::FixedIncome, self.bond_shock);
        shocks.insert(AssetClass::Commodities, self.commodity_shock);
        shocks.insert(AssetClass::Currencies, self.fx_shock);
        shocks.insert(AssetClass::Derivatives, self.equity_shock); // Correlate with equities
        shocks.insert(AssetClass::Alternatives, self.equity_shock); // Correlate with equities
        shocks
    }

    /// Apply shocks to a list of positions
    pub fn apply_shocks_to_positions(&self, positions: &[Position]) -> Vec<Position> {
        let shocks = self.generate_market_shocks();

        positions
            .iter()
            .map(|pos| {
                let asset_class = self.get_asset_class_for_symbol(&pos.symbol);
                let shock = shocks.get(&asset_class).unwrap_or(&Decimal::ZERO);
                let shock_multiplier = 1.0 + shock.to_f64().unwrap_or(0.0);
                let new_market_price = pos.market_price * shock_multiplier;

                Position {
                    market_price: new_market_price,
                    market_value: pos.quantity * new_market_price,
                    unrealized_pnl: (new_market_price - pos.average_cost) * pos.quantity,
                    last_updated: Utc::now().timestamp(),
                    ..pos.clone()
                }
            })
            .collect()
    }

    /// Create a formal stress test scenario record
    pub fn create_stress_scenario(&self) -> risk::risk_types::StressScenario {
        let mut price_shocks = HashMap::new();
        price_shocks.insert(
            "EQUITY".to_string(),
            self.equity_shock.to_f64().unwrap_or(0.0),
        );
        price_shocks.insert("BOND".to_string(), self.bond_shock.to_f64().unwrap_or(0.0));
        price_shocks.insert(
            "COMMODITY".to_string(),
            self.commodity_shock.to_f64().unwrap_or(0.0),
        );
        price_shocks.insert("FX".to_string(), self.fx_shock.to_f64().unwrap_or(0.0));

        risk::risk_types::StressScenario {
            id: Uuid::new_v4().to_string(),
            name: self.name.clone(),
            price_shocks: price_shocks.clone(),
            market_shocks: price_shocks,
            volatility_multiplier: 1.0 + self.volatility_shock.to_f64().unwrap_or(0.0),
            volatility_multipliers: HashMap::new(),
            correlation_changes: HashMap::new(),
            correlation_adjustments: HashMap::new(),
            liquidity_haircuts: HashMap::new(),
        }
    }

    fn get_asset_class_for_symbol(&self, symbol: &str) -> AssetClass {
        if symbol.starts_with("TEST_EQ_") {
            AssetClass::Equities
        } else if symbol.starts_with("TEST_FX_") {
            AssetClass::Currencies
        } else if symbol.starts_with("TEST_FUT_") {
            AssetClass::Derivatives
        } else if symbol.starts_with("TEST_BOND_") {
            AssetClass::FixedIncome
        } else if symbol.starts_with("TEST_COMM_") {
            AssetClass::Commodities
        } else if symbol.starts_with("TEST_CRYPTO_") {
            AssetClass::Alternatives
        } else {
            AssetClass::Equities // Default
        }
    }
}

/// Interest rate shock scenario
#[derive(Debug, Clone)]
pub struct InterestRateShockScenario {
    pub name: String,
    pub description: String,
    pub rate_shock: Decimal,      // +200 basis points
    pub duration_impact: Decimal, // -10% for 10 year duration
}

impl Default for InterestRateShockScenario {
    fn default() -> Self {
        Self::new()
    }
}

impl InterestRateShockScenario {
    pub fn new() -> Self {
        Self {
            name: "Interest Rate Shock".to_string(),
            description: "200bp parallel shift in yield curve".to_string(),
            rate_shock: Decimal::new(200, 4), // 2.00% = 200 basis points
            duration_impact: Decimal::new(-10, 2), // -10%
        }
    }

    /// Apply duration-based shock to bond positions
    pub fn apply_duration_shock(&self, positions: &[Position]) -> Vec<Position> {
        positions
            .iter()
            .map(|pos| {
                if pos.symbol.starts_with("TEST_BOND_") {
                    // Duration not stored in Position, use default 5 year duration for bonds
                    let duration = 5.0;
                    let price_impact = -duration * self.rate_shock.to_f64().unwrap_or(0.0); // Duration × rate change
                    let shock_multiplier = 1.0 + (price_impact / 100.0);
                    let new_market_price = pos.market_price * shock_multiplier;

                    Position {
                        market_price: new_market_price,
                        market_value: pos.quantity * new_market_price,
                        unrealized_pnl: (new_market_price - pos.average_cost) * pos.quantity,
                        last_updated: Utc::now().timestamp(),
                        ..pos.clone()
                    }
                } else {
                    pos.clone()
                }
            })
            .collect()
    }
}

// =============================================================================
// HIGH FREQUENCY TRADING SCENARIOS
// =============================================================================

/// High frequency trading scenario with rapid order flow
#[derive(Debug, Clone)]
pub struct HighFrequencyScenario {
    pub symbol: String,
    pub base_price: Decimal,
    pub tick_size: Decimal,
    pub order_rate_per_second: usize,
    pub volatility: Decimal,
}

impl Default for HighFrequencyScenario {
    fn default() -> Self {
        Self::new()
    }
}

impl HighFrequencyScenario {
    pub fn new() -> Self {
        Self {
            symbol: TEST_EQUITY_1.to_string(),
            base_price: Decimal::from(100),
            tick_size: Decimal::new(1, 2), // $0.01
            order_rate_per_second: 1000,
            volatility: Decimal::new(2, 2), // 2% volatility
        }
    }

    pub fn with_symbol(mut self, symbol: impl Into<String>) -> Self {
        self.symbol = symbol.into();
        self
    }

    pub fn with_order_rate(mut self, rate: usize) -> Self {
        self.order_rate_per_second = rate;
        self
    }

    /// Generate rapid order flow for testing
    pub fn generate_order_flow(&self, duration_seconds: u64) -> Vec<TestOrder> {
        let total_orders = (duration_seconds as usize) * self.order_rate_per_second;
        let mut orders = Vec::with_capacity(total_orders);
        let start_time = Utc::now();

        for i in 0..total_orders {
            let timestamp = start_time
                + ChronoDuration::milliseconds(
                    (i as i64 * 1000) / self.order_rate_per_second as i64,
                );
            let side = if i % 2 == 0 {
                OrderSide::Buy
            } else {
                OrderSide::Sell
            };
            let price_offset = (i % 10) as i64 - 5; // -5 to +5 ticks
            let price = self.base_price + (self.tick_size * Decimal::from(price_offset));
            let quantity = Decimal::from(100 + (i % 900)); // 100 to 1000 shares

            orders.push(TestOrder {
                id: Uuid::new_v4(),
                symbol: self.symbol.clone(),
                side,
                quantity,
                price,
                order_type: OrderType::Limit,
                timestamp,
                time_in_force: TimeInForce::Day,
            });
        }

        orders
    }

    /// Generate market data tick stream
    pub fn generate_market_ticks(&self, count: usize) -> Vec<MarketTick> {
        let mut ticks = Vec::with_capacity(count);
        let mut current_price = self.base_price;
        let start_time = Utc::now();

        for i in 0..count {
            let timestamp = start_time + ChronoDuration::microseconds(i as i64 * 1000); // 1ms intervals

            // Random walk price movement
            let price_change = if i % 3 == 0 {
                self.tick_size
            } else if i % 3 == 1 {
                -self.tick_size
            } else {
                Decimal::ZERO
            };

            current_price += price_change;

            ticks.push(MarketTick {
                symbol: self.symbol.clone(),
                timestamp,
                bid: current_price - self.tick_size,
                ask: current_price + self.tick_size,
                last: current_price,
                volume: Decimal::from(100 + (i % 1000)),
                sequence: i as u64,
            });
        }

        ticks
    }
}

// =============================================================================
// RISK MANAGEMENT SCENARIOS
// =============================================================================

/// Risk limit breach scenario
#[derive(Debug, Clone)]
pub struct RiskLimitBreachScenario {
    pub portfolio_id: String,
    pub var_limit: Decimal,
    pub position_limit: Decimal,
    pub concentration_limit: Decimal,
}

impl Default for RiskLimitBreachScenario {
    fn default() -> Self {
        Self::new()
    }
}

impl RiskLimitBreachScenario {
    pub fn new() -> Self {
        Self {
            portfolio_id: TEST_PORTFOLIO_1.to_string(),
            var_limit: Decimal::from(100000), // $100k VaR limit
            position_limit: Decimal::from(1000000), // $1M position limit
            concentration_limit: Decimal::new(25, 2), // 25% concentration limit
        }
    }

    /// Create positions that breach concentration limits
    pub fn create_concentrated_positions(&self) -> Vec<Position> {
        let _total_portfolio_value = Decimal::from(1000000);

        vec![
            // Concentrated position - 40% of portfolio (breaches 25% limit)
            PositionBuilder::new()
                .with_portfolio_id(&self.portfolio_id)
                .with_symbol(TEST_EQUITY_1)
                .with_quantity(Decimal::from(4000))
                .with_average_price(Decimal::from(100))
                .with_market_price(Decimal::from(100))
                .with_weight(Decimal::new(40, 2))
                .build(),
            // Normal positions
            PositionBuilder::new()
                .with_portfolio_id(&self.portfolio_id)
                .with_symbol(TEST_EQUITY_2)
                .with_quantity(Decimal::from(3000))
                .with_average_price(Decimal::from(100))
                .with_market_price(Decimal::from(100))
                .with_weight(Decimal::new(30, 2))
                .build(),
            PositionBuilder::new()
                .with_portfolio_id(&self.portfolio_id)
                .with_symbol(TEST_EQUITY_3)
                .with_quantity(Decimal::from(3000))
                .with_average_price(Decimal::from(100))
                .with_market_price(Decimal::from(100))
                .with_weight(Decimal::new(30, 2))
                .build(),
        ]
    }

    /// Create positions that would breach VaR limits under stress
    pub fn create_high_var_positions(&self) -> Vec<Position> {
        // High beta, high volatility positions
        vec![
            PositionBuilder::new()
                .with_portfolio_id(&self.portfolio_id)
                .with_symbol(TEST_EQUITY_1)
                .with_quantity(Decimal::from(5000))
                .with_average_price(Decimal::from(100))
                .with_market_price(Decimal::from(100))
                .with_beta(Decimal::new(20, 1)) // Beta of 2.0
                .build(),
            PositionBuilder::new()
                .with_portfolio_id(&self.portfolio_id)
                .with_symbol(TEST_EQUITY_2)
                .with_quantity(Decimal::from(3000))
                .with_average_price(Decimal::from(100))
                .with_market_price(Decimal::from(100))
                .with_beta(Decimal::new(18, 1)) // Beta of 1.8
                .build(),
        ]
    }
}

// =============================================================================
// SUPPORTING DATA STRUCTURES
// =============================================================================

/// Test order structure for order flow scenarios
#[derive(Debug, Clone)]
pub struct TestOrder {
    pub id: Uuid,
    pub symbol: String,
    pub side: OrderSide,
    pub quantity: Decimal,
    pub price: Decimal,
    pub order_type: OrderType,
    pub timestamp: DateTime<Utc>,
    pub time_in_force: TimeInForce,
}

/// Market tick data for price feed scenarios
#[derive(Debug, Clone)]
pub struct MarketTick {
    pub symbol: String,
    pub timestamp: DateTime<Utc>,
    pub bid: Decimal,
    pub ask: Decimal,
    pub last: Decimal,
    pub volume: Decimal,
    pub sequence: u64,
}

/// Enums for order testing
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OrderSide {
    Buy,
    Sell,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OrderType {
    Market,
    Limit,
    Stop,
    StopLimit,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TimeInForce {
    Day,
    GoodTillCanceled,
    ImmediateOrCancel,
    FillOrKill,
}

// =============================================================================
// SCENARIO FACTORY
// =============================================================================

/// Factory for creating predefined test scenarios
#[derive(Debug)]
pub struct ScenarioFactory;

impl ScenarioFactory {
    /// Create a basic balanced portfolio scenario
    pub fn basic_portfolio() -> (Portfolio, Vec<Instrument>, Vec<Position>) {
        let scenario = BasicTradingScenario::new();
        let portfolio = scenario.create_portfolio();
        let instruments = scenario.create_instruments();
        let positions = scenario.create_positions();
        (portfolio, instruments, positions)
    }

    /// Create a market crash stress test scenario
    pub fn market_crash() -> (risk::risk_types::StressScenario, Vec<Position>) {
        let crash_scenario = MarketCrashScenario::new();
        let basic_scenario = BasicTradingScenario::new();
        let original_positions = basic_scenario.create_positions();
        let stressed_positions = crash_scenario.apply_shocks_to_positions(&original_positions);
        let stress_scenario = crash_scenario.create_stress_scenario();
        (stress_scenario, stressed_positions)
    }

    /// Create a high frequency trading scenario
    pub fn high_frequency_trading(duration_seconds: u64) -> (Vec<TestOrder>, Vec<MarketTick>) {
        let hft_scenario = HighFrequencyScenario::new();
        let orders = hft_scenario.generate_order_flow(duration_seconds);
        let ticks = hft_scenario.generate_market_ticks((duration_seconds * 1000) as usize); // 1 tick per ms
        (orders, ticks)
    }

    /// Create a risk limit breach scenario
    pub fn risk_limit_breach() -> (Portfolio, Vec<Position>) {
        let risk_scenario = RiskLimitBreachScenario::new();
        let portfolio = PortfolioBuilder::new()
            .with_id(&risk_scenario.portfolio_id)
            .with_var_limit(risk_scenario.var_limit)
            .build();
        let positions = risk_scenario.create_concentrated_positions();
        (portfolio, positions)
    }

    /// Create a multi-asset diversified scenario
    pub fn multi_asset_diversified() -> (Portfolio, Vec<Instrument>, Vec<Position>) {
        let portfolio = PortfolioBuilder::new()
            .with_id("MULTI_ASSET_PORTFOLIO")
            .with_name("Multi-Asset Diversified Portfolio")
            .build();

        let instruments = BatchBuilder::create_diverse_instruments(10);
        let symbols: Vec<&str> = instruments.iter().map(|i| i.symbol.as_str()).collect();
        let positions = BatchBuilder::create_test_positions("MULTI_ASSET_PORTFOLIO", &symbols);

        (portfolio, instruments, positions)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_basic_trading_scenario() {
        let scenario = BasicTradingScenario::new();
        let portfolio = scenario.create_portfolio();
        let positions = scenario.create_positions();
        let instruments = scenario.create_instruments();

        assert_eq!(portfolio.id, TEST_PORTFOLIO_1);
        assert!(!positions.is_empty());
        assert_eq!(positions.len(), instruments.len());

        // Check portfolio value adds up
        let total_value: Decimal = positions.iter().map(|p| p.market_value.to_decimal()).sum();
        assert!((total_value - scenario.total_value).abs() < Decimal::new(1, 0));
        // Within $1
    }

    #[test]
    fn test_market_crash_scenario() {
        let crash_scenario = MarketCrashScenario::new();
        let basic_scenario = BasicTradingScenario::new();
        let original_positions = basic_scenario.create_positions();
        let stressed_positions = crash_scenario.apply_shocks_to_positions(&original_positions);

        assert_eq!(original_positions.len(), stressed_positions.len());

        // Check that equity positions went down
        for (original, stressed) in original_positions
            .into_iter()
            .zip(stressed_positions.into_iter())
        {
            if original.symbol.starts_with("TEST_EQ_") {
                assert!(stressed.market_price < original.market_price);
                assert!(stressed.unrealized_pnl < original.unrealized_pnl);
            }
        }
    }

    #[test]
    fn test_high_frequency_scenario() {
        let hft_scenario = HighFrequencyScenario::new();
        let orders = hft_scenario.generate_order_flow(5); // 5 seconds
        let ticks = hft_scenario.generate_market_ticks(100);

        assert_eq!(orders.len(), 5 * hft_scenario.order_rate_per_second);
        assert_eq!(ticks.len(), 100);

        // Check order timestamps are sequential
        for window in orders.windows(2) {
            assert!(window[1].timestamp >= window[0].timestamp);
        }
    }

    #[test]
    fn test_risk_limit_breach_scenario() {
        let risk_scenario = RiskLimitBreachScenario::new();
        let positions = risk_scenario.create_concentrated_positions();

        // Check that first position breaches concentration limit
        // Position has market value of $400,000 (4000 shares * $100)
        // Total portfolio is $1,000,000, so weight is 40% = 0.40
        let concentrated_position = &positions[0];
        let total_portfolio_value = 1_000_000.0;
        let position_weight = concentrated_position.market_value / total_portfolio_value;
        assert!(position_weight > risk_scenario.concentration_limit.to_f64().unwrap_or(0.0));
    }

    #[test]
    fn test_scenario_factory() {
        let (portfolio, instruments, positions) = ScenarioFactory::basic_portfolio();
        assert!(!positions.is_empty());
        assert_eq!(positions.len(), instruments.len());
        assert_eq!(portfolio.id, TEST_PORTFOLIO_1);

        let (stress_scenario, stressed_positions) = ScenarioFactory::market_crash();
        assert!(!stressed_positions.is_empty());
        // StressScenario doesn't have scenario_type field - just verify it has a name
        assert!(!stress_scenario.name.is_empty());

        let (orders, ticks) = ScenarioFactory::high_frequency_trading(1);
        assert!(!orders.is_empty());
        assert!(!ticks.is_empty());
    }
}
