//! Symbol classification and configuration management for trading instruments.
//!
//! This module provides comprehensive symbol classification and configuration
//! management for various financial instruments in the Foxhunt HFT trading system.
//! It handles asset classification, volatility profiles, trading hours, and
//! market-specific parameters for optimal trading execution.

use chrono::{DateTime, Datelike, NaiveDate, NaiveTime, Utc, Weekday};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::Duration;
use uuid::Uuid;

/// Asset classification enumeration for different financial instrument types.
///
/// Provides standardized classification for all tradeable instruments,
/// enabling type-specific risk management, execution logic, and regulatory
/// compliance across different asset classes.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum AssetClassification {
    /// Equity securities (stocks, ADRs, REITs)
    Equity,
    /// Futures contracts (commodities, financials, indices)
    Future,
    /// Foreign exchange pairs (major, minor, exotic)
    Forex,
    /// Cryptocurrency and digital assets
    Crypto,
    /// Physical commodities (metals, energy, agriculture)
    Commodity,
    /// Fixed income securities (bonds, notes, bills)
    FixedIncome,
    /// Options contracts (equity, index, commodity options)
    Option,
    /// Exchange-traded funds and products
    Etf,
    /// Indices and benchmark instruments
    Index,
    /// Structured products and derivatives
    Derivative,
}

impl AssetClassification {
    /// Returns the regulatory classification for compliance purposes.
    pub const fn regulatory_class(&self) -> &'static str {
        match self {
            AssetClassification::Equity => "EQUITY",
            AssetClassification::Future => "FUTURE",
            AssetClassification::Forex => "FX",
            AssetClassification::Crypto => "CRYPTO",
            AssetClassification::Commodity => "COMMODITY",
            AssetClassification::FixedIncome => "FIXED_INCOME",
            AssetClassification::Option => "OPTION",
            AssetClassification::Etf => "ETF",
            AssetClassification::Index => "INDEX",
            AssetClassification::Derivative => "DERIVATIVE",
        }
    }

    /// Returns whether this asset class requires T+1 settlement.
    pub const fn requires_t_plus_one_settlement(&self) -> bool {
        matches!(self, AssetClassification::Equity | AssetClassification::Etf)
    }

    /// Returns whether this asset class supports after-hours trading.
    pub const fn supports_extended_hours(&self) -> bool {
        matches!(
            self,
            AssetClassification::Equity
                | AssetClassification::Etf
                | AssetClassification::Forex
                | AssetClassification::Crypto
        )
    }
}

/// Volatility profile configuration for risk management and position sizing.
///
/// Defines volatility characteristics and risk parameters for different
/// instruments, enabling dynamic position sizing and risk-adjusted execution.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VolatilityProfile {
    /// Historical average volatility (annualized)
    pub average_volatility: f64,
    /// Maximum observed volatility (99th percentile)
    pub max_volatility: f64,
    /// Minimum observed volatility (1st percentile)
    pub min_volatility: f64,
    /// Beta coefficient relative to market index
    pub beta: f64,
    /// Average True Range (ATR) for recent period
    pub atr: f64,
    /// Correlation with market benchmark
    pub market_correlation: f64,
    /// Volatility regime classification
    pub volatility_regime: VolatilityRegime,
    /// Last updated timestamp for volatility metrics
    pub last_updated: DateTime<Utc>,
    /// Number of observations used for calculation
    pub sample_size: u32,
}

impl VolatilityProfile {
    /// Creates a new volatility profile with default values.
    pub fn new() -> Self {
        Self {
            average_volatility: 0.20,
            max_volatility: 1.00,
            min_volatility: 0.05,
            beta: 1.0,
            atr: 0.0,
            market_correlation: 0.0,
            volatility_regime: VolatilityRegime::Normal,
            last_updated: Utc::now(),
            sample_size: 0,
        }
    }

    /// Updates volatility metrics with new data point.
    pub fn update_metrics(&mut self, new_volatility: f64, new_atr: f64) {
        // Update exponential moving average
        {
            let alpha = 0.1_f64; // Smoothing factor
            #[allow(clippy::float_arithmetic)]
            let one_minus_alpha = 1.0_f64 - alpha;
            #[allow(clippy::float_arithmetic)]
            let volatility_term = one_minus_alpha * self.average_volatility;
            self.average_volatility = alpha.mul_add(new_volatility, volatility_term);

            #[allow(clippy::float_arithmetic)]
            let atr_term = one_minus_alpha * self.atr;
            self.atr = alpha.mul_add(new_atr, atr_term);
        }
        self.last_updated = Utc::now();
        self.sample_size = self.sample_size.saturating_add(1);

        // Update volatility regime
        self.volatility_regime = self.classify_regime();
    }

    /// Classifies current volatility regime based on metrics.
    fn classify_regime(&self) -> VolatilityRegime {
        #[allow(clippy::float_arithmetic)]
        let volatility_ratio = self.average_volatility / 0.20_f64; // Relative to 20% baseline

        if volatility_ratio > 2.0_f64 {
            VolatilityRegime::High
        } else if volatility_ratio > 1.5 {
            VolatilityRegime::Elevated
        } else if volatility_ratio < 0.5 {
            VolatilityRegime::Low
        } else {
            VolatilityRegime::Normal
        }
    }

    /// Returns risk-adjusted position size multiplier.
    pub const fn position_size_multiplier(&self) -> f64 {
        match self.volatility_regime {
            VolatilityRegime::Low => 1.5,
            VolatilityRegime::Normal => 1.0,
            VolatilityRegime::Elevated => 0.7,
            VolatilityRegime::High => 0.4,
        }
    }
}

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

/// Volatility regime classification for risk management.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum VolatilityRegime {
    /// Low volatility environment (< 50% of normal)
    Low,
    /// Normal volatility environment
    Normal,
    /// Elevated volatility (50-100% above normal)
    Elevated,
    /// High volatility environment (> 100% above normal)
    High,
}

/// Trading hours configuration for different markets and sessions.
///
/// Defines market operating hours, pre-market and after-hours sessions,
/// and holiday schedules for accurate trade timing and execution.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TradingHours {
    /// Primary market timezone identifier (e.g., "America/New_York")
    pub timezone: String,
    /// Regular trading session start time
    pub market_open: NaiveTime,
    /// Regular trading session end time
    pub market_close: NaiveTime,
    /// Pre-market session start time (optional)
    pub pre_market_open: Option<NaiveTime>,
    /// After-hours session end time (optional)
    pub after_hours_close: Option<NaiveTime>,
    /// Trading days of the week
    pub trading_days: Vec<Weekday>,
    /// Market holidays (dates when market is closed)
    pub holidays: Vec<NaiveDate>,
    /// Half-day sessions with early close times
    pub half_days: HashMap<NaiveDate, NaiveTime>,
}

impl TradingHours {
    /// Creates US equity market trading hours configuration.
    #[allow(clippy::unwrap_used)] // Uses hardcoded time values that are guaranteed to be valid
    pub fn us_equity() -> Self {
        Self {
            timezone: "America/New_York".to_owned(),
            market_open: NaiveTime::from_hms_opt(9, 30, 0).unwrap(),
            market_close: NaiveTime::from_hms_opt(16, 0, 0).unwrap(),
            pre_market_open: Some(NaiveTime::from_hms_opt(4, 0, 0).unwrap()),
            after_hours_close: Some(NaiveTime::from_hms_opt(20, 0, 0).unwrap()),
            trading_days: vec![
                Weekday::Mon,
                Weekday::Tue,
                Weekday::Wed,
                Weekday::Thu,
                Weekday::Fri,
            ],
            holidays: vec![],
            half_days: HashMap::new(),
        }
    }

    /// Creates 24/7 trading hours for crypto markets.
    #[allow(clippy::unwrap_used)] // Uses hardcoded time values that are guaranteed to be valid
    pub fn crypto_24_7() -> Self {
        Self {
            timezone: "UTC".to_owned(),
            market_open: NaiveTime::from_hms_opt(0, 0, 0).unwrap(),
            market_close: NaiveTime::from_hms_opt(23, 59, 59).unwrap(),
            pre_market_open: None,
            after_hours_close: None,
            trading_days: vec![
                Weekday::Mon,
                Weekday::Tue,
                Weekday::Wed,
                Weekday::Thu,
                Weekday::Fri,
                Weekday::Sat,
                Weekday::Sun,
            ],
            holidays: vec![],
            half_days: HashMap::new(),
        }
    }

    /// Creates forex market trading hours (Sunday 5 PM to Friday 5 PM EST).
    #[allow(clippy::unwrap_used)] // Uses hardcoded time values that are guaranteed to be valid
    pub fn forex() -> Self {
        Self {
            timezone: "America/New_York".to_owned(),
            market_open: NaiveTime::from_hms_opt(17, 0, 0).unwrap(),
            market_close: NaiveTime::from_hms_opt(17, 0, 0).unwrap(),
            pre_market_open: None,
            after_hours_close: None,
            trading_days: vec![
                Weekday::Sun,
                Weekday::Mon,
                Weekday::Tue,
                Weekday::Wed,
                Weekday::Thu,
                Weekday::Fri,
            ],
            holidays: vec![],
            half_days: HashMap::new(),
        }
    }

    /// Checks if market is currently open.
    pub fn is_market_open(&self, current_time: DateTime<Utc>) -> bool {
        // Convert to market timezone and check if within trading hours
        // This is a simplified implementation - production would use proper timezone handling
        let current_date = current_time.date_naive();
        let current_time = current_time.time();
        let current_weekday = current_date.weekday();

        // Check if it's a trading day
        if !self.trading_days.contains(&current_weekday) {
            return false;
        }

        // Check if it's a holiday
        if self.holidays.contains(&current_date) {
            return false;
        }

        // Check if within trading hours
        current_time >= self.market_open && current_time <= self.market_close
    }

    /// Checks if extended hours trading is active.
    pub fn is_extended_hours_open(&self, current_time: DateTime<Utc>) -> bool {
        let current_time = current_time.time();

        // Check pre-market
        if let Some(pre_open) = self.pre_market_open {
            if current_time >= pre_open && current_time < self.market_open {
                return true;
            }
        }

        // Check after-hours
        if let Some(after_close) = self.after_hours_close {
            if current_time > self.market_close && current_time <= after_close {
                return true;
            }
        }

        false
    }
}

impl Default for TradingHours {
    fn default() -> Self {
        Self::us_equity()
    }
}

/// Comprehensive symbol configuration containing all trading parameters.
///
/// Central configuration structure for each tradeable symbol, containing
/// classification, market parameters, risk settings, and execution rules.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SymbolConfig {
    /// Unique symbol identifier
    pub symbol: String,
    /// Symbol description or company name
    pub description: String,
    /// Asset classification
    pub classification: AssetClassification,
    /// Volatility and risk profile
    pub volatility_profile: VolatilityProfile,
    /// Market operating hours
    pub trading_hours: TradingHours,
    /// Minimum price increment (tick size)
    pub tick_size: f64,
    /// Standard trading unit size
    pub lot_size: f64,
    /// Minimum order quantity
    pub min_order_size: f64,
    /// Maximum order quantity
    pub max_order_size: f64,
    /// Primary exchange or venue
    pub primary_exchange: String,
    /// Currency denomination
    pub currency: String,
    /// Sector classification (for equities)
    pub sector: Option<String>,
    /// Industry classification (for equities)
    pub industry: Option<String>,
    /// Market capitalization (for equities)
    pub market_cap: Option<f64>,
    /// Average daily volume
    pub avg_daily_volume: f64,
    /// Margin requirements
    pub margin_requirement: f64,
    /// Position limits
    pub position_limit: Option<f64>,
    /// Risk multiplier for position sizing
    pub risk_multiplier: f64,
    /// Configuration metadata
    pub metadata: SymbolMetadata,
}

impl SymbolConfig {
    /// Creates a new symbol configuration with default values.
    pub fn new(symbol: String, classification: AssetClassification) -> Self {
        let trading_hours = match classification {
            AssetClassification::Crypto => TradingHours::crypto_24_7(),
            AssetClassification::Forex => TradingHours::forex(),
            _ => TradingHours::us_equity(),
        };

        Self {
            symbol: symbol.clone(),
            description: format!("{} - Auto-generated", symbol),
            classification,
            volatility_profile: VolatilityProfile::new(),
            trading_hours,
            tick_size: 0.01,
            lot_size: 1.0,
            min_order_size: 1.0,
            max_order_size: 1_000_000.0,
            primary_exchange: "".to_owned(),
            currency: "USD".to_owned(),
            sector: None,
            industry: None,
            market_cap: None,
            avg_daily_volume: 0.0,
            margin_requirement: 0.25,
            position_limit: None,
            risk_multiplier: 1.0,
            metadata: SymbolMetadata::new(),
        }
    }

    /// Validates the symbol configuration for correctness.
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub fn validate(&self) -> Result<(), String> {
        if self.symbol.is_empty() {
            return Err("Symbol cannot be empty".to_owned());
        }

        if self.tick_size <= 0.0_f64 {
            return Err("Tick size must be positive".to_owned());
        }

        if self.lot_size <= 0.0_f64 {
            return Err("Lot size must be positive".to_owned());
        }

        if self.min_order_size <= 0.0_f64 {
            return Err("Minimum order size must be positive".to_owned());
        }

        if self.max_order_size <= self.min_order_size {
            return Err("Maximum order size must be greater than minimum".to_owned());
        }

        if self.margin_requirement < 0.0_f64 || self.margin_requirement > 1.0_f64 {
            return Err("Margin requirement must be between 0 and 1".to_owned());
        }

        Ok(())
    }

    /// Calculates the effective position size based on risk parameters.
    pub fn calculate_position_size(&self, base_size: f64, _account_value: f64) -> f64 {
        let volatility_multiplier = self.volatility_profile.position_size_multiplier();
        let risk_adjusted_size = base_size
            .mul_add(volatility_multiplier, 0.0)
            .mul_add(self.risk_multiplier, 0.0);

        // Apply position limits
        if let Some(limit) = self.position_limit {
            risk_adjusted_size.min(limit)
        } else {
            risk_adjusted_size
        }
    }

    /// Returns the appropriate tick size for a given price level.
    pub const fn get_tick_size_for_price(&self, _price: f64) -> f64 {
        // Some markets have variable tick sizes based on price
        // This is a simplified implementation
        self.tick_size
    }

    /// Rounds price to the nearest valid tick.
    pub fn round_to_tick(&self, price: f64) -> f64 {
        let tick = self.get_tick_size_for_price(price);
        #[allow(clippy::float_arithmetic)]
        let result = (price / tick).round() * tick;
        result
    }

    /// Checks if the symbol is currently tradeable.
    pub fn is_tradeable(&self, current_time: DateTime<Utc>) -> bool {
        self.trading_hours.is_market_open(current_time) && self.metadata.is_active
    }

    /// Checks if extended hours trading is available.
    pub const fn supports_extended_hours(&self) -> bool {
        self.classification.supports_extended_hours()
    }
}

/// Symbol configuration metadata for versioning and tracking.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SymbolMetadata {
    /// Unique configuration ID
    pub id: Uuid,
    /// Configuration version
    pub version: u32,
    /// Creation timestamp
    pub created_at: DateTime<Utc>,
    /// Last update timestamp
    pub updated_at: DateTime<Utc>,
    /// Active status
    pub is_active: bool,
    /// Data source for configuration
    pub data_source: String,
    /// Last validation timestamp
    pub last_validated: Option<DateTime<Utc>>,
    /// Configuration tags for organization
    pub tags: Vec<String>,
}

impl SymbolMetadata {
    /// Creates new metadata with default values.
    pub fn new() -> Self {
        let now = Utc::now();
        Self {
            id: Uuid::new_v4(),
            version: 1,
            created_at: now,
            updated_at: now,
            is_active: true,
            data_source: "manual".to_owned(),
            last_validated: None,
            tags: vec![],
        }
    }

    /// Updates the metadata timestamp and version.
    pub fn update(&mut self) {
        self.updated_at = Utc::now();
        self.version = self.version.saturating_add(1);
    }

    /// Marks the configuration as validated.
    pub fn mark_validated(&mut self) {
        self.last_validated = Some(Utc::now());
    }
}

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

/// Symbol configuration manager for loading and caching symbol configurations.
///
/// Provides high-performance access to symbol configurations with caching,
/// hot-reload capabilities, and configuration validation.
#[derive(Debug)]
#[allow(clippy::module_name_repetitions)]
pub struct SymbolConfigManager {
    /// In-memory cache of symbol configurations
    symbol_cache: HashMap<String, SymbolConfig>,
    /// Last cache update timestamp
    last_updated: DateTime<Utc>,
    /// Cache timeout duration
    cache_timeout: Duration,
}

impl SymbolConfigManager {
    /// Creates a new symbol configuration manager.
    pub fn new() -> Self {
        Self {
            symbol_cache: HashMap::new(),
            last_updated: Utc::now(),
            cache_timeout: Duration::from_secs(300), // 5 minutes
        }
    }

    /// Loads symbol configuration from cache or source.
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub async fn get_symbol_config(
        &mut self,
        symbol: &str,
    ) -> Result<Option<SymbolConfig>, String> {
        // Check cache first
        if let Some(config) = self.symbol_cache.get(symbol) {
            if !self.is_cache_expired() {
                return Ok(Some(config.clone()));
            }
        }

        // Load from source (this would integrate with database/external source)
        self.load_symbol_from_source(symbol).await
    }

    /// Loads all symbol configurations into cache.
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub async fn load_all_symbols(&mut self) -> Result<usize, String> {
        // This would integrate with the database or external configuration source
        self.refresh_cache().await
    }

    /// Adds or updates a symbol configuration.
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub fn upsert_symbol_config(&mut self, config: SymbolConfig) -> Result<(), String> {
        // Validate configuration
        config.validate()?;

        // Update cache
        self.symbol_cache.insert(config.symbol.clone(), config);
        self.last_updated = Utc::now();

        Ok(())
    }

    /// Removes a symbol configuration.
    pub fn remove_symbol_config(&mut self, symbol: &str) -> Option<SymbolConfig> {
        self.symbol_cache.remove(symbol)
    }

    /// Returns all cached symbol configurations.
    pub fn get_all_symbols(&self) -> Vec<&SymbolConfig> {
        self.symbol_cache.values().collect()
    }

    /// Returns symbols filtered by asset classification.
    pub fn get_symbols_by_classification(
        &self,
        classification: &AssetClassification,
    ) -> Vec<&SymbolConfig> {
        self.symbol_cache
            .values()
            .filter(|config| &config.classification == classification)
            .collect()
    }

    /// Checks if cache has expired.
    fn is_cache_expired(&self) -> bool {
        Utc::now()
            .signed_duration_since(self.last_updated)
            .to_std()
            .unwrap_or(Duration::MAX)
            > self.cache_timeout
    }

    /// Loads symbol configuration from external source.
    async fn load_symbol_from_source(
        &mut self,
        _symbol: &str,
    ) -> Result<Option<SymbolConfig>, String> {
        // This would integrate with database or external configuration API
        // For now, return None to indicate symbol not found

        // Example of creating a default config if needed:
        // let config = SymbolConfig::new(symbol.to_owned(), AssetClassification::Equity);
        // self.symbol_cache.insert(symbol.to_owned(), config.clone());
        // Ok(Some(config))

        Ok(None)
    }

    /// Refreshes the entire symbol cache from source.
    async fn refresh_cache(&mut self) -> Result<usize, String> {
        // This would integrate with database to load all active symbols
        // For now, return the current cache size
        Ok(self.symbol_cache.len())
    }

    /// Sets cache timeout duration.
    pub const fn set_cache_timeout(&mut self, timeout: Duration) {
        self.cache_timeout = timeout;
    }

    /// Forces cache refresh on next access.
    pub const fn invalidate_cache(&mut self) {
        self.last_updated = DateTime::<Utc>::MIN_UTC;
    }

    /// Returns cache statistics.
    pub fn cache_stats(&self) -> (usize, DateTime<Utc>, bool) {
        (
            self.symbol_cache.len(),
            self.last_updated,
            self.is_cache_expired(),
        )
    }
}

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

#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
    use super::*;

    #[test]
    fn test_asset_classification_regulatory_class() {
        assert_eq!(AssetClassification::Equity.regulatory_class(), "EQUITY");
        assert_eq!(AssetClassification::Forex.regulatory_class(), "FX");
        assert_eq!(AssetClassification::Crypto.regulatory_class(), "CRYPTO");
    }

    #[test]
    fn test_volatility_profile_update() {
        let mut profile = VolatilityProfile::new();
        profile.update_metrics(0.40, 2.5);

        // With exponential smoothing: 0.1 * 0.40 + 0.9 * 0.20 = 0.22
        assert!(profile.average_volatility > 0.20 && profile.average_volatility < 0.25);
        // With exponential smoothing: 0.1 * 2.5 + 0.9 * 0.0 = 0.25
        assert!((profile.atr - 0.25).abs() < 0.01);
        assert_eq!(profile.volatility_regime, VolatilityRegime::Normal);
    }

    #[test]
    fn test_symbol_config_validation() {
        let mut config = SymbolConfig::new("AAPL".to_owned(), AssetClassification::Equity);
        assert!(config.validate().is_ok());

        config.tick_size = -0.01;
        assert!(config.validate().is_err());
    }

    #[test]
    fn test_trading_hours_us_equity() {
        let hours = TradingHours::us_equity();
        assert_eq!(hours.timezone, "America/New_York");
        assert_eq!(
            hours.market_open,
            NaiveTime::from_hms_opt(9, 30, 0).unwrap()
        );
        assert_eq!(
            hours.market_close,
            NaiveTime::from_hms_opt(16, 0, 0).unwrap()
        );
    }

    #[test]
    fn test_symbol_config_manager() {
        let mut manager = SymbolConfigManager::new();
        let config = SymbolConfig::new("TEST".to_owned(), AssetClassification::Equity);

        assert!(manager.upsert_symbol_config(config).is_ok());
        assert_eq!(manager.get_all_symbols().len(), 1);
    }
}
