//! Test Data Builders for Foxhunt HFT Trading System
//!
//! This module provides builder patterns for creating test data objects
//! with sensible defaults and fluent configuration APIs.
//!
//! ## Usage
//!
//! ```rust
//! use tests::fixtures::builders::{PortfolioBuilder, InstrumentBuilder, PositionBuilder};
//! use tests::fixtures::{TEST_EQUITY_1, TEST_PORTFOLIO_1};
//!
//! // Build a test portfolio
//! let portfolio = PortfolioBuilder::new()
//!     .with_id(TEST_PORTFOLIO_1)
//!     .with_name("Test Portfolio")
//!     .with_base_currency("USD")
//!     .build();
//!
//! // Build a test instrument
//! let instrument = InstrumentBuilder::new()
//!     .with_symbol(TEST_EQUITY_1)
//!     .with_name("Test Equity 1")
//!     .equity()
//!     .build();
//!
//! // Build a test position
//! let position = PositionBuilder::new()
//!     .with_portfolio_id(TEST_PORTFOLIO_1)
//!     .with_symbol(TEST_EQUITY_1)
//!     .with_quantity(Decimal::from(100))
//!     .with_price(Decimal::from(150))
//!     .build();
//! ```

use ::rust_decimal::prelude::ToPrimitive;
use ::rust_decimal::Decimal;
use chrono::{DateTime, Utc};
use serde_json::json;
use uuid::Uuid;

// Import Position from risk crate to match scenarios.rs usage
use common::types::Price;
use risk::risk_types::Position;

use crate::fixtures::helpers::ToDecimal;

// Use local test fixture types defined in mod.rs
use super::*;

// =============================================================================
// INSTRUMENT BUILDER
// =============================================================================

/// Builder for creating test Instrument objects
#[derive(Debug, Clone)]
pub struct InstrumentBuilder {
    id: Uuid,
    symbol: String,
    isin: Option<String>,
    cusip: Option<String>,
    bloomberg_id: Option<String>,
    reuters_id: Option<String>,
    name: String,
    instrument_type: InstrumentType,
    asset_class: AssetClass,
    sector: Option<MarketSector>,
    currency: String,
    exchange: Option<String>,
    tick_size: Option<Decimal>,
    lot_size: Option<Decimal>,
    multiplier: Option<Decimal>,
    maturity_date: Option<DateTime<Utc>>,
    strike_price: Option<Decimal>,
    option_type: Option<String>,
    underlying_symbol: Option<String>,
    is_active: bool,
    created_at: DateTime<Utc>,
    updated_at: DateTime<Utc>,
    metadata: serde_json::Value,
}

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

impl InstrumentBuilder {
    pub fn new() -> Self {
        let now = Utc::now();
        Self {
            id: Uuid::new_v4(),
            symbol: TEST_EQUITY_1.to_string(),
            isin: None,
            cusip: None,
            bloomberg_id: None,
            reuters_id: None,
            name: "Test Instrument".to_string(),
            instrument_type: InstrumentType::Equity,
            asset_class: AssetClass::Equities,
            sector: Some(MarketSector::Technology),
            currency: "USD".to_string(),
            exchange: Some("TEST_EXCHANGE".to_string()),
            tick_size: Some(Decimal::new(1, 2)), // 0.01
            lot_size: Some(Decimal::from(1)),
            multiplier: Some(Decimal::from(1)),
            maturity_date: None,
            strike_price: None,
            option_type: None,
            underlying_symbol: None,
            is_active: true,
            created_at: now,
            updated_at: now,
            metadata: json!({"test_data": true}),
        }
    }

    pub fn with_id(mut self, id: Uuid) -> Self {
        self.id = id;
        self
    }

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

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

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

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

    pub fn with_sector(mut self, sector: MarketSector) -> Self {
        self.sector = Some(sector);
        self
    }

    pub fn with_tick_size(mut self, tick_size: Decimal) -> Self {
        self.tick_size = Some(tick_size);
        self
    }

    pub fn with_lot_size(mut self, lot_size: Decimal) -> Self {
        self.lot_size = Some(lot_size);
        self
    }

    pub fn with_multiplier(mut self, multiplier: Decimal) -> Self {
        self.multiplier = Some(multiplier);
        self
    }

    pub fn with_maturity_date(mut self, maturity_date: DateTime<Utc>) -> Self {
        self.maturity_date = Some(maturity_date);
        self
    }

    pub fn with_strike_price(mut self, strike_price: Decimal) -> Self {
        self.strike_price = Some(strike_price);
        self
    }

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

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

    pub fn inactive(mut self) -> Self {
        self.is_active = false;
        self
    }

    pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
        self.metadata = metadata;
        self
    }

    // Asset class convenience methods
    pub fn equity(mut self) -> Self {
        self.instrument_type = InstrumentType::Equity;
        self.asset_class = AssetClass::Equities;
        self.sector = Some(MarketSector::Technology);
        self
    }

    pub fn bond(mut self) -> Self {
        self.instrument_type = InstrumentType::Bond;
        self.asset_class = AssetClass::FixedIncome;
        self.sector = Some(MarketSector::Financials);
        self
    }

    pub fn currency(mut self) -> Self {
        self.instrument_type = InstrumentType::Currency;
        self.asset_class = AssetClass::Currencies;
        self.sector = None;
        self
    }

    pub fn future(mut self) -> Self {
        self.instrument_type = InstrumentType::Future;
        self.asset_class = AssetClass::Derivatives;
        self.sector = None;
        self
    }

    pub fn option(mut self) -> Self {
        self.instrument_type = InstrumentType::Option;
        self.asset_class = AssetClass::Derivatives;
        self.sector = None;
        self
    }

    pub fn commodity(mut self) -> Self {
        self.instrument_type = InstrumentType::Commodity;
        self.asset_class = AssetClass::Commodities;
        self.sector = Some(MarketSector::Materials);
        self
    }

    pub fn crypto(mut self) -> Self {
        self.instrument_type = InstrumentType::Crypto;
        self.asset_class = AssetClass::Alternatives;
        self.sector = None;
        self
    }

    pub fn build(self) -> Instrument {
        Instrument {
            id: self.id,
            symbol: self.symbol,
            isin: self.isin,
            cusip: self.cusip,
            bloomberg_id: self.bloomberg_id,
            reuters_id: self.reuters_id,
            name: self.name,
            instrument_type: self.instrument_type,
            asset_class: self.asset_class,
            sector: self.sector,
            currency: self.currency,
            exchange: self.exchange,
            tick_size: self.tick_size,
            lot_size: self.lot_size,
            multiplier: self.multiplier,
            maturity_date: self.maturity_date,
            strike_price: self.strike_price,
            option_type: self.option_type,
            underlying_symbol: self.underlying_symbol,
            is_active: self.is_active,
            created_at: self.created_at,
            updated_at: self.updated_at,
            metadata: self.metadata,
        }
    }
}

// =============================================================================
// PORTFOLIO BUILDER
// =============================================================================

/// Builder for creating test Portfolio objects
#[derive(Debug, Clone)]
pub struct PortfolioBuilder {
    id: String,
    name: String,
    description: Option<String>,
    base_currency: String,
    portfolio_type: String,
    inception_date: DateTime<Utc>,
    manager_id: String,
    benchmark: Option<String>,
    risk_budget: Option<Decimal>,
    var_limit: Option<Decimal>,
    max_drawdown_limit: Option<Decimal>,
    is_active: bool,
    created_at: DateTime<Utc>,
    updated_at: DateTime<Utc>,
    metadata: serde_json::Value,
}

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

impl PortfolioBuilder {
    pub fn new() -> Self {
        let now = Utc::now();
        Self {
            id: TEST_PORTFOLIO_1.to_string(),
            name: "Test Portfolio".to_string(),
            description: Some("Test portfolio for automated testing".to_string()),
            base_currency: "USD".to_string(),
            portfolio_type: "Test".to_string(),
            inception_date: now,
            manager_id: "test_manager".to_string(),
            benchmark: Some("SPY".to_string()),
            risk_budget: Some(Decimal::new(15, 2)), // 0.15 (15%)
            var_limit: Some(Decimal::from(100000)),
            max_drawdown_limit: Some(Decimal::new(20, 2)), // 0.20 (20%)
            is_active: true,
            created_at: now,
            updated_at: now,
            metadata: json!({"test_data": true}),
        }
    }

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

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

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

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

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

    pub fn with_inception_date(mut self, date: DateTime<Utc>) -> Self {
        self.inception_date = date;
        self
    }

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

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

    pub fn with_risk_budget(mut self, risk_budget: Decimal) -> Self {
        self.risk_budget = Some(risk_budget);
        self
    }

    pub fn with_var_limit(mut self, var_limit: Decimal) -> Self {
        self.var_limit = Some(var_limit);
        self
    }

    pub fn with_max_drawdown_limit(mut self, limit: Decimal) -> Self {
        self.max_drawdown_limit = Some(limit);
        self
    }

    pub fn inactive(mut self) -> Self {
        self.is_active = false;
        self
    }

    pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
        self.metadata = metadata;
        self
    }

    // Portfolio type convenience methods
    pub fn strategy_portfolio(mut self) -> Self {
        self.portfolio_type = "Strategy".to_string();
        self
    }

    pub fn hedge_fund(mut self) -> Self {
        self.portfolio_type = "Hedge Fund".to_string();
        self
    }

    pub fn long_only(mut self) -> Self {
        self.portfolio_type = "Long Only".to_string();
        self
    }

    pub fn market_neutral(mut self) -> Self {
        self.portfolio_type = "Market Neutral".to_string();
        self
    }

    pub fn build(self) -> Portfolio {
        Portfolio {
            id: self.id,
            name: self.name,
            description: self.description,
            base_currency: self.base_currency,
            portfolio_type: self.portfolio_type,
            inception_date: self.inception_date,
            manager_id: self.manager_id,
            benchmark: self.benchmark,
            risk_budget: self.risk_budget,
            var_limit: self.var_limit,
            max_drawdown_limit: self.max_drawdown_limit,
            is_active: self.is_active,
            created_at: self.created_at,
            updated_at: self.updated_at,
            metadata: self.metadata,
        }
    }
}

// =============================================================================
// POSITION BUILDER
// =============================================================================

/// Builder for creating test Position objects (risk::risk_types::Position)
#[derive(Debug, Clone)]
pub struct PositionBuilder {
    symbol: String,
    quantity: f64,
    market_price: f64,
    market_value: f64,
    average_cost: f64,
    average_price: Price,
    unrealized_pnl: f64,
    realized_pnl: f64,
    last_updated: i64,
    // Additional builder fields for convenience (not in final Position)
    _portfolio_id: Option<String>,
    _weight: Option<f64>,
    _beta: Option<f64>,
    _duration: Option<f64>,
}

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

impl PositionBuilder {
    pub fn new() -> Self {
        let quantity = 100.0;
        let price = 100.0;
        let market_value = quantity * price;

        Self {
            symbol: TEST_EQUITY_1.to_string(),
            quantity,
            market_price: price,
            market_value,
            average_cost: price,
            average_price: Price::from_f64(price).unwrap_or_else(|_| Price::new(0.0).unwrap()),
            unrealized_pnl: 0.0,
            realized_pnl: 0.0,
            last_updated: Utc::now().timestamp(),
            _portfolio_id: Some(TEST_PORTFOLIO_1.to_string()),
            _weight: Some(0.05), // 5%
            _beta: Some(1.2),
            _duration: None,
        }
    }

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

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

    pub fn with_quantity(mut self, quantity: Decimal) -> Self {
        self.quantity = quantity.to_f64().unwrap_or(0.0);
        // Recalculate market value
        self.market_value = self.quantity * self.market_price;
        self.unrealized_pnl = (self.market_price - self.average_cost) * self.quantity;
        self
    }

    pub fn with_average_price(mut self, price: Decimal) -> Self {
        let price_f64 = price.to_f64().unwrap_or(0.0);
        self.average_cost = price_f64;
        self.average_price =
            Price::from_f64(price_f64).unwrap_or_else(|_| Price::new(0.0).unwrap());
        // Recalculate unrealized PnL
        self.unrealized_pnl = (self.market_price - price_f64) * self.quantity;
        self
    }

    pub fn with_market_price(mut self, price: Decimal) -> Self {
        let price_f64 = price.to_f64().unwrap_or(0.0);
        self.market_price = price_f64;
        // Recalculate market value and unrealized PnL
        self.market_value = self.quantity * price_f64;
        self.unrealized_pnl = (price_f64 - self.average_cost) * self.quantity;
        self
    }

    pub fn with_weight(mut self, weight: Decimal) -> Self {
        self._weight = Some(weight.to_f64().unwrap_or(0.0));
        self
    }

    pub fn with_beta(mut self, beta: Decimal) -> Self {
        self._beta = Some(beta.to_f64().unwrap_or(0.0));
        self
    }

    pub fn with_duration(mut self, duration: Decimal) -> Self {
        self._duration = Some(duration.to_f64().unwrap_or(0.0));
        self
    }

    // Position type convenience methods
    pub fn long_position(mut self, quantity: i64) -> Self {
        self.quantity = quantity.abs() as f64;
        self.market_value = self.quantity * self.market_price;
        self.unrealized_pnl = (self.market_price - self.average_cost) * self.quantity;
        self
    }

    pub fn short_position(mut self, quantity: i64) -> Self {
        self.quantity = -(quantity.abs() as f64);
        self.market_value = self.quantity * self.market_price;
        self.unrealized_pnl = (self.market_price - self.average_cost) * self.quantity;
        self
    }

    pub fn profitable(mut self, profit_pct: f64) -> Self {
        let profit_multiplier = 1.0 + (profit_pct / 100.0);
        let new_market_price = self.average_cost * profit_multiplier;
        self.market_price = new_market_price;
        self.market_value = self.quantity * new_market_price;
        self.unrealized_pnl = (new_market_price - self.average_cost) * self.quantity;
        self
    }

    pub fn losing(mut self, loss_pct: f64) -> Self {
        let loss_multiplier = 1.0 - (loss_pct / 100.0);
        let new_market_price = self.average_cost * loss_multiplier;
        self.market_price = new_market_price;
        self.market_value = self.quantity * new_market_price;
        self.unrealized_pnl = (new_market_price - self.average_cost) * self.quantity;
        self
    }

    pub fn build(self) -> Position {
        Position {
            symbol: self.symbol,
            quantity: self.quantity,
            market_price: self.market_price,
            market_value: self.market_value,
            average_cost: self.average_cost,
            average_price: self.average_price,
            unrealized_pnl: self.unrealized_pnl,
            realized_pnl: self.realized_pnl,
            last_updated: self.last_updated,
        }
    }
}

// =============================================================================
// COUNTERPARTY BUILDER
// =============================================================================

/// Builder for creating test Counterparty objects
#[derive(Debug, Clone)]
pub struct CounterpartyBuilder {
    id: String,
    name: String,
    counterparty_type: String,
    country: String,
    credit_rating: Option<String>,
    lei_code: Option<String>,
    parent_company: Option<String>,
    is_active: bool,
    exposure_limit: Option<Decimal>,
    margin_requirement: Option<Decimal>,
    netting_agreement: bool,
    created_at: DateTime<Utc>,
    updated_at: DateTime<Utc>,
    metadata: serde_json::Value,
}

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

impl CounterpartyBuilder {
    pub fn new() -> Self {
        let now = Utc::now();
        Self {
            id: "TEST_COUNTERPARTY_001".to_string(),
            name: "Test Counterparty".to_string(),
            counterparty_type: "Bank".to_string(),
            country: "US".to_string(),
            credit_rating: Some("AA".to_string()),
            lei_code: None,
            parent_company: None,
            is_active: true,
            exposure_limit: Some(Decimal::from(10000000)), // $10M
            margin_requirement: Some(Decimal::new(5, 2)),  // 5%
            netting_agreement: true,
            created_at: now,
            updated_at: now,
            metadata: json!({"test_data": true}),
        }
    }

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

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

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

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

    pub fn with_credit_rating(mut self, rating: impl Into<String>) -> Self {
        self.credit_rating = Some(rating.into());
        self
    }

    pub fn with_exposure_limit(mut self, limit: Decimal) -> Self {
        self.exposure_limit = Some(limit);
        self
    }

    pub fn with_margin_requirement(mut self, margin: Decimal) -> Self {
        self.margin_requirement = Some(margin);
        self
    }

    pub fn without_netting_agreement(mut self) -> Self {
        self.netting_agreement = false;
        self
    }

    pub fn inactive(mut self) -> Self {
        self.is_active = false;
        self
    }

    // Counterparty type convenience methods
    pub fn bank(mut self) -> Self {
        self.counterparty_type = "Bank".to_string();
        self.credit_rating = Some("AA".to_string());
        self
    }

    pub fn broker(mut self) -> Self {
        self.counterparty_type = "Broker".to_string();
        self.credit_rating = Some("A".to_string());
        self
    }

    pub fn exchange(mut self) -> Self {
        self.counterparty_type = "Exchange".to_string();
        self.credit_rating = Some("AAA".to_string());
        self
    }

    pub fn hedge_fund(mut self) -> Self {
        self.counterparty_type = "Hedge Fund".to_string();
        self.credit_rating = Some("BBB".to_string());
        self
    }

    pub fn build(self) -> Counterparty {
        Counterparty {
            id: self.id,
            name: self.name,
            counterparty_type: self.counterparty_type,
            country: self.country,
            credit_rating: self.credit_rating,
            lei_code: self.lei_code,
            parent_company: self.parent_company,
            is_active: self.is_active,
            exposure_limit: self.exposure_limit,
            margin_requirement: self.margin_requirement,
            netting_agreement: self.netting_agreement,
            created_at: self.created_at,
            updated_at: self.updated_at,
            metadata: self.metadata,
        }
    }
}

// =============================================================================
// BATCH BUILDERS
// =============================================================================

/// Utility for building multiple test objects
#[derive(Debug)]
pub struct BatchBuilder;

impl BatchBuilder {
    /// Create multiple test instruments with different asset classes
    pub fn create_diverse_instruments(count: usize) -> Vec<Instrument> {
        let asset_classes = [
            AssetClass::Equities,
            AssetClass::Currencies,
            AssetClass::FixedIncome,
            AssetClass::Derivatives,
            AssetClass::Commodities,
            AssetClass::Alternatives,
        ];

        (0..count)
            .map(|i| {
                let asset_class = asset_classes[i % asset_classes.len()];
                let symbol = generate_test_symbol(asset_class);

                let mut builder = InstrumentBuilder::new()
                    .with_symbol(&symbol)
                    .with_name(format!("Test Instrument {}", i + 1));

                builder = match asset_class {
                    AssetClass::Equities => builder.equity(),
                    AssetClass::Currencies => builder.currency(),
                    AssetClass::FixedIncome => builder.bond(),
                    AssetClass::Derivatives => builder.future(),
                    AssetClass::Commodities => builder.commodity(),
                    AssetClass::Alternatives => builder.crypto(),
                    AssetClass::Cash => builder.equity(), // Default to equity for cash
                };

                builder.build()
            })
            .collect()
    }

    /// Create multiple test portfolios
    pub fn create_test_portfolios(count: usize) -> Vec<Portfolio> {
        (0..count)
            .map(|i| {
                PortfolioBuilder::new()
                    .with_id(format!("TEST_PORTFOLIO_{:03}", i + 1))
                    .with_name(format!("Test Portfolio {}", i + 1))
                    .with_manager_id(format!("test_manager_{}", i + 1))
                    .build()
            })
            .collect()
    }

    /// Create multiple test positions for a portfolio
    pub fn create_test_positions(portfolio_id: &str, symbols: &[&str]) -> Vec<Position> {
        symbols
            .iter()
            .enumerate()
            .map(|(i, &symbol)| {
                let quantity = Decimal::from((i + 1) * 100);
                let price = get_test_price_for_symbol(symbol).to_decimal();

                PositionBuilder::new()
                    .with_portfolio_id(portfolio_id)
                    .with_symbol(symbol)
                    .with_quantity(quantity)
                    .with_average_price(price)
                    .with_market_price(price)
                    .build()
            })
            .collect()
    }
}

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

    #[test]
    fn test_instrument_builder() {
        let instrument = InstrumentBuilder::new()
            .with_symbol("TEST_SYMBOL")
            .with_name("Test Name")
            .equity()
            .build();

        assert_eq!(instrument.symbol, "TEST_SYMBOL");
        assert_eq!(instrument.name, "Test Name");
        assert_eq!(instrument.instrument_type, InstrumentType::Equity);
        assert_eq!(instrument.asset_class, AssetClass::Equities);
        assert!(instrument.is_active);
    }

    #[test]
    fn test_portfolio_builder() {
        let portfolio = PortfolioBuilder::new()
            .with_id("TEST_PORT")
            .with_name("Test Portfolio")
            .strategy_portfolio()
            .build();

        assert_eq!(portfolio.id, "TEST_PORT");
        assert_eq!(portfolio.name, "Test Portfolio");
        assert_eq!(portfolio.portfolio_type, "Strategy");
        assert!(portfolio.is_active);
    }

    #[test]
    fn test_position_builder() {
        let position = PositionBuilder::new()
            .with_symbol("TEST")
            .long_position(500)
            .profitable(10.0)
            .build();

        assert_eq!(position.symbol, "TEST");
        assert_eq!(position.quantity, 500.0);
        assert!(position.unrealized_pnl > 0.0);
    }

    #[test]
    fn test_batch_builder() {
        let instruments = BatchBuilder::create_diverse_instruments(6);
        assert_eq!(instruments.len(), 6);

        // Should have different asset classes
        let asset_classes: std::collections::HashSet<_> =
            instruments.iter().map(|i| i.asset_class).collect();
        assert!(asset_classes.len() > 1);
    }
}
