//! # Universe Selection ML Module
//!
//! Dynamic universe selection using machine learning for optimal asset filtering.
//! Implements liquidity scoring, momentum ranking, volatility clustering, and correlation analysis.

#![deny(clippy::unwrap_used, clippy::expect_used)]
#![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used))]
#![allow(clippy::module_name_repetitions)]
#![allow(clippy::integer_division)]
// Workspace denies that need local overrides for universe code patterns
#![allow(clippy::indexing_slicing)] // Matrix indexing in correlation/volatility code
#![allow(clippy::shadow_reuse, clippy::shadow_same, clippy::shadow_unrelated)] // Common in scoring/ranking code
#![allow(clippy::partial_pub_fields)] // ML config structs: some fields are pub API, some internal
#![allow(clippy::same_name_method)] // Intentional: inherent methods shadow trait defaults
#![allow(clippy::non_ascii_literal)] // Math symbols in ML documentation
#![allow(clippy::similar_names)] // ML naming: min_val/max_val, state/states are conventional

pub use ml_core::MLError;
pub use ml_core::PRECISION_FACTOR;

pub mod correlation;
pub mod liquidity;
pub mod momentum;
pub mod volatility;

use chrono::{DateTime, Utc};
use std::collections::HashMap;
use std::time::SystemTime;

use serde::{Deserialize, Serialize};

use common::{Price, Symbol, Volume};

// Missing types that need to be defined
#[derive(Debug)]
#[derive(Default)]
pub struct LiquidityScorer {
    config: LiquidityScorerConfig,
}


impl LiquidityScorer {
    pub const fn new(config: LiquidityScorerConfig) -> Result<Self, MLError> {
        Ok(Self { config })
    }

    pub fn calculate_liquidity_score(
        &self,
        asset: &AssetMetadata,
    ) -> Result<LiquidityScore, MLError> {
        let base_score = asset.market_cap_usd * self.config.volume_weight;
        let spread_penalty = asset.spread * self.config.spread_weight;
        let depth_bonus = asset.depth * self.config.depth_weight;

        let score = base_score - spread_penalty + depth_bonus;

        Ok(LiquidityScore {
            overall_score: score,
            score,
            volume: asset.volume,
            spread: asset.spread,
            depth: asset.depth,
            timestamp: SystemTime::now(),
        })
    }
}

#[derive(Debug, Clone, Default)]
pub struct LiquidityScorerConfig {
    pub min_volume_threshold: f64,
    pub spread_weight: f64,
    pub depth_weight: f64,
    pub volume_weight: f64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LiquidityScore {
    pub overall_score: f64,
    pub score: f64,
    pub volume: f64,
    pub spread: f64,
    pub depth: f64,
    pub timestamp: SystemTime,
}

impl Default for LiquidityScore {
    fn default() -> Self {
        Self {
            overall_score: 0.0,
            score: 0.0,
            volume: 0.0,
            spread: 0.0,
            depth: 0.0,
            timestamp: SystemTime::now(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MomentumScore {
    pub overall_score: f64,
    pub score: f64,
    pub momentum_1d: f64,
    pub momentum_7d: f64,
    pub momentum_30d: f64,
    pub timestamp: SystemTime,
}

impl Default for MomentumScore {
    fn default() -> Self {
        Self {
            overall_score: 0.0,
            score: 0.0,
            momentum_1d: 0.0,
            momentum_7d: 0.0,
            momentum_30d: 0.0,
            timestamp: SystemTime::now(),
        }
    }
}

#[derive(Debug)]
#[derive(Default)]
pub struct MomentumRanker {
    config: MomentumRankerConfig,
}


impl MomentumRanker {
    pub const fn new(config: MomentumRankerConfig) -> Result<Self, MLError> {
        Ok(Self { config })
    }

    pub fn calculate_momentum_score(
        &self,
        asset: &AssetMetadata,
    ) -> Result<MomentumScore, MLError> {
        let momentum = asset.momentum * self.config.momentum_weight;
        let reversal_penalty = asset.reversal_risk * self.config.reversal_weight;

        let score = momentum - reversal_penalty;

        Ok(MomentumScore {
            overall_score: score,
            score,
            momentum_1d: asset.momentum,
            momentum_7d: asset.momentum * 0.8,  // Simplified
            momentum_30d: asset.momentum * 0.6, // Simplified
            timestamp: SystemTime::now(),
        })
    }
}

#[derive(Debug, Clone, Default)]
pub struct MomentumRankerConfig {
    pub lookback_days: u32,
    pub momentum_weight: f64,
    pub reversal_weight: f64,
}

#[derive(Debug)]
#[derive(Default)]
pub struct CorrelationAnalyzer {
    _config: CorrelationAnalyzerConfig,
}

impl CorrelationAnalyzer {
    pub const fn new(config: CorrelationAnalyzerConfig) -> Result<Self, MLError> {
        Ok(Self { _config: config })
    }
}


#[derive(Debug, Clone, Default)]
pub struct CorrelationAnalyzerConfig {
    pub correlation_threshold: f64,
    pub window_size: usize,
    pub update_frequency_ms: u64,
}

#[derive(Debug)]
#[derive(Default)]
pub struct VolatilityClusterEngine {
    _config: VolatilityClusterEngineConfig,
}

impl VolatilityClusterEngine {
    pub const fn new(config: VolatilityClusterEngineConfig) -> Result<Self, MLError> {
        Ok(Self { _config: config })
    }
}


#[derive(Debug, Clone, Default)]
pub struct VolatilityClusterEngineConfig {
    pub cluster_count: usize,
    pub volatility_window: usize,
    pub min_volume_threshold: f64,
}

// Additional required types
#[derive(Debug, Clone, Default)]
pub struct AssetMetadata {
    pub symbol: String,
    pub market_cap_usd: f64,
    pub price: f64,
    pub volume_24h: f64,
    pub volume: f64,
    pub spread: f64,
    pub depth: f64,
    pub momentum: f64,
    pub reversal_risk: f64,
    pub volatility: f64,
    pub sector: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CorrelationMatrix {
    pub correlations: HashMap<(Symbol, Symbol), f64>,
    pub eigenvalues: Vec<f64>,
    pub condition_number: f64,
    pub timestamp: SystemTime,
}

impl Default for CorrelationMatrix {
    fn default() -> Self {
        Self {
            correlations: HashMap::new(),
            eigenvalues: Vec::new(),
            condition_number: 1.0,
            timestamp: SystemTime::now(),
        }
    }
}

// Additional types are defined above - main configuration types follow

/// Configuration for universe selection engine
#[derive(Debug, Clone, Serialize, Deserialize)]
/// `UniverseSelectionConfig` component.
pub struct UniverseSelectionConfig {
    /// Maximum number of assets to include in the universe
    pub max_universe_size: usize,

    /// Minimum market cap threshold
    pub min_market_cap: f64,

    /// Minimum trading `volume`
    pub min_volume: f64,

    /// Minimum liquidity score
    pub min_liquidity_score: f64,

    /// Minimum momentum score
    pub min_momentum_score: f64,

    /// Enable correlation filtering
    pub enable_correlation_filtering: bool,

    /// Maximum correlation threshold
    pub max_correlation: f64,

    /// Rebalancing frequency in days
    pub rebalancing_frequency_days: usize,

    /// Cache expiry time in seconds
    pub cache_expiry_seconds: u64,
}

impl Default for UniverseSelectionConfig {
    fn default() -> Self {
        Self {
            max_universe_size: 500,
            min_market_cap: 1_000_000_000.0, // $1B minimum market cap
            min_volume: 1_000_000.0,         // $1M daily volume
            min_liquidity_score: 0.3,
            min_momentum_score: 0.3,
            enable_correlation_filtering: true,
            max_correlation: 0.8,
            rebalancing_frequency_days: 30,
            cache_expiry_seconds: 300, // 5 minutes
        }
    }
}

/// Asset data for universe selection
#[derive(Debug, Clone, Serialize, Deserialize)]
/// `AssetData` component.
pub struct AssetData {
    pub asset_id: Symbol,
    pub symbol: String,
    pub price: Price,
    pub volume: Volume,
    pub market_cap: u64,
    pub sector: String,
    pub exchange: String,
    pub last_trade_time: DateTime<Utc>,
}

/// Universe selection criteria
#[derive(Debug, Clone, Serialize, Deserialize)]
/// `SelectionCriteria` component.
pub struct SelectionCriteria {
    pub min_liquidity_score: f64,
    pub min_momentum_score: f64,
    pub max_correlation: f64,
    pub volatility_regime: Option<String>, // Simplified for now
    pub sector_limits: HashMap<String, usize>,
    pub max_assets: usize,
    pub min_market_cap: u64,
    pub exclude_penny_stocks: bool,
}

impl Default for SelectionCriteria {
    fn default() -> Self {
        Self {
            min_liquidity_score: 0.6,
            min_momentum_score: 0.5,
            max_correlation: 0.8,
            volatility_regime: None,
            sector_limits: HashMap::new(),
            max_assets: 100,
            min_market_cap: 1_000_000_000, // $1B minimum market cap
            exclude_penny_stocks: true,
        }
    }
}

/// Selected universe with scores
#[derive(Debug, Clone, Serialize, Deserialize)]
/// `SelectedUniverse` component.
pub struct SelectedUniverse {
    pub assets: Vec<AssetData>,
    pub liquidity_scores: HashMap<Symbol, LiquidityScore>,
    pub momentum_scores: HashMap<Symbol, MomentumScore>,
    pub correlation_matrix: CorrelationMatrix,
    pub diversification_score: f64, // Replace with DiversificationScore when diversification module is implemented
    pub selection_timestamp: DateTime<Utc>,
    pub rebalance_needed: bool,
}

/// Universe selection performance metrics
#[derive(Debug, Clone, Serialize, Deserialize)]
/// `SelectionMetrics` component.
pub struct SelectionMetrics {
    pub total_candidates: usize,
    pub selected_assets: usize,
    pub avg_liquidity_score: f64,
    pub avg_momentum_score: f64,
    pub correlation_efficiency: f64,
    pub sector_distribution: HashMap<String, usize>,
    pub selection_latency_micros: u64,
    pub cache_hit_rate: f64,
}

/// Configuration for universe selection models
#[derive(Debug, Clone, Serialize, Deserialize)]
/// `UniverseConfig` component.
pub struct UniverseConfig {
    pub update_frequency_minutes: u32,
    pub lookback_days: u32,
    pub feature_dimensions: usize,
    pub batch_size: usize,
    pub cache_size: usize,
    pub enable_real_time: bool,
    pub enable_regime_detection: bool,
    pub parallel_processing: bool,
}

impl Default for UniverseConfig {
    fn default() -> Self {
        Self {
            update_frequency_minutes: 60,
            lookback_days: 252, // 1 year of trading days
            feature_dimensions: 64,
            batch_size: 64,    // Default batch size
            cache_size: 10000, // Default cache size
            enable_real_time: true,
            enable_regime_detection: true,
            parallel_processing: true,
        }
    }
}

/// Asset ranking for universe selection
#[derive(Debug, Clone, Serialize, Deserialize)]
/// `AssetRanking` component.
pub struct AssetRanking {
    pub asset_id: Symbol,
    pub symbol: Symbol,
    pub liquidity_rank: usize,
    pub momentum_rank: usize,
    pub volatility_rank: usize,
    pub correlation_score: f64,
    pub composite_score: f64,
    pub percentile_rank: f64,
    pub sector: String,
    pub market_cap_rank: usize,
    pub is_selected: bool,
    pub selection_confidence: f64,
}

/// Universe update event
#[derive(Debug, Clone, Serialize, Deserialize)]
/// `UniverseUpdate` component.
pub struct UniverseUpdate {
    pub timestamp: SystemTime,
    pub added_assets: Vec<Symbol>,
    pub removed_assets: Vec<Symbol>,
    pub ranking_changes: HashMap<Symbol, AssetRanking>,
    pub selection_criteria_used: SelectionCriteria,
    pub update_reason: UniverseUpdateReason,
    pub performance_metrics: UniversePerformanceMetrics,
}

/// Reason for universe update
#[derive(Debug, Clone, Serialize, Deserialize)]
/// `UniverseUpdateReason` component.
pub enum UniverseUpdateReason {
    Scheduled,
    ThresholdBreached,
    MarketRegimeChange,
    LiquidityChange,
    VolatilitySpike,
    Manual,
}

/// Universe performance metrics
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct UniversePerformanceMetrics {
    pub total_universe_size: usize,
    pub avg_liquidity_score: f64,
    pub avg_volatility: f64,
    pub sector_diversification: f64,
    pub correlation_efficiency: f64,
    pub turnover_rate: f64,
    pub selection_latency_ms: u64,
}

/// Universe selection engine
#[derive(Debug)]
pub struct UniverseSelectionEngine {
    _config: UniverseConfig,
    criteria: SelectionCriteria,

    // Scoring engines
    liquidity_scorer: LiquidityScorer,
    momentum_ranker: MomentumRanker,
    _correlation_analyzer: CorrelationAnalyzer,
    _volatility_engine: VolatilityClusterEngine,

    // Current state
    current_universe: HashMap<Symbol, AssetRanking>,
    candidate_assets: Vec<AssetData>,
    performance_history: Vec<UniversePerformanceMetrics>,

    // Cache and optimization
    scoring_cache: HashMap<Symbol, (SystemTime, HashMap<String, f64>)>,
    last_update: SystemTime,
    update_count: u64,
}

impl UniverseSelectionEngine {
    /// Create new universe selection engine
    pub fn new(config: UniverseConfig, criteria: SelectionCriteria) -> Result<Self, MLError> {
        let liquidity_scorer = LiquidityScorer::default();
        let momentum_ranker = MomentumRanker::default();
        let correlation_analyzer = CorrelationAnalyzer::default();
        let volatility_engine = VolatilityClusterEngine::default();

        Ok(Self {
            _config: config,
            criteria,
            liquidity_scorer,
            momentum_ranker,
            _correlation_analyzer: correlation_analyzer,
            _volatility_engine: volatility_engine,
            current_universe: HashMap::new(),
            candidate_assets: Vec::new(),
            performance_history: Vec::new(),
            scoring_cache: HashMap::new(),
            last_update: SystemTime::now(),
            update_count: 0,
        })
    }

    /// Update candidate assets
    pub fn update_candidates(&mut self, assets: Vec<AssetData>) -> Result<(), MLError> {
        self.candidate_assets = assets;
        Ok(())
    }

    /// Select universe based on current criteria
    pub fn select_universe(&mut self) -> Result<UniverseUpdate, MLError> {
        let start_time = SystemTime::now();

        // Score all candidate assets
        let mut asset_rankings = Vec::new();
        let candidate_assets = self.candidate_assets.clone(); // Clone to avoid borrowing issues

        for asset in &candidate_assets {
            if let Ok(ranking) = self.score_asset(asset) {
                asset_rankings.push(ranking);
            }
        }

        // Sort by composite score
        asset_rankings.sort_by(|a, b| {
            b.composite_score
                .partial_cmp(&a.composite_score)
                .unwrap_or(std::cmp::Ordering::Equal)
        });

        // Apply selection criteria
        let selected_assets = self.apply_selection_criteria(&asset_rankings)?;

        // Calculate changes from current universe
        let mut added_assets = Vec::new();
        let mut removed_assets = Vec::new();
        let mut ranking_changes = HashMap::new();

        for ranking in &selected_assets {
            if !self.current_universe.contains_key(&ranking.asset_id) {
                added_assets.push(ranking.asset_id.clone());
            }
            ranking_changes.insert(ranking.asset_id.clone(), ranking.clone());
        }

        for asset_id in self.current_universe.keys() {
            if !selected_assets.iter().any(|r| r.asset_id == *asset_id) {
                removed_assets.push(asset_id.clone());
            }
        }

        // Update current universe
        self.current_universe.clear();
        for ranking in selected_assets {
            let asset_id = ranking.asset_id.clone();
            self.current_universe.insert(asset_id, ranking);
        }

        // Calculate performance metrics
        let performance_metrics = self.calculate_performance_metrics()?;
        self.performance_history.push(performance_metrics.clone());

        // Create update event
        let update = UniverseUpdate {
            timestamp: start_time,
            added_assets,
            removed_assets,
            ranking_changes,
            selection_criteria_used: self.criteria.clone(),
            update_reason: UniverseUpdateReason::Scheduled,
            performance_metrics,
        };

        self.last_update = start_time;
        self.update_count += 1;

        Ok(update)
    }

    /// Score a single asset
    fn score_asset(&mut self, asset: &AssetData) -> Result<AssetRanking, MLError> {
        // Check cache first
        if let Some((timestamp, cached_scores)) = self.scoring_cache.get(&asset.asset_id) {
            if timestamp.elapsed().unwrap_or_default().as_secs() < 300 {
                // 5 minute cache
                return self.create_ranking_from_cache(asset, cached_scores);
            }
        }

        // Create asset metadata from asset data
        let asset_metadata = AssetMetadata {
            symbol: asset.symbol.clone(),
            market_cap_usd: 1_000_000_000.0, // Default market cap
            price: asset.price.to_f64(),
            volume_24h: asset.volume.to_f64(),
            volume: asset.volume.to_f64(),
            spread: 0.001,      // Default spread
            depth: 100_000.0,   // Default depth
            momentum: 0.05,     // Default momentum
            reversal_risk: 0.1, // Default reversal risk
            volatility: 0.15,   // Default volatility
            sector: asset.sector.clone(),
        };

        // Calculate fresh scores
        let liquidity_score = self
            .liquidity_scorer
            .calculate_liquidity_score(&asset_metadata)?;

        let momentum_score = self
            .momentum_ranker
            .calculate_momentum_score(&asset_metadata)?;

        // Simplified scoring - in production would be more sophisticated
        let composite_score =
            liquidity_score.overall_score * 0.4 + momentum_score.overall_score * 0.6;

        let ranking = AssetRanking {
            asset_id: asset.asset_id.clone(),
            symbol: asset.symbol.clone().into(),
            liquidity_rank: 0, // Would be calculated after sorting
            momentum_rank: 0,  // Would be calculated after sorting
            volatility_rank: 0,
            correlation_score: 0.5, // Default neutral correlation
            composite_score,
            percentile_rank: 0.0, // Would be calculated after sorting
            sector: asset.sector.clone(),
            market_cap_rank: 0,
            is_selected: false,
            selection_confidence: composite_score.min(1.0),
        };

        // Cache the scores
        let mut scores = HashMap::new();
        scores.insert("liquidity".to_owned(), liquidity_score.overall_score);
        scores.insert("momentum".to_owned(), momentum_score.overall_score);
        scores.insert("composite".to_owned(), composite_score);

        self.scoring_cache
            .insert(asset.asset_id.clone(), (SystemTime::now(), scores));

        Ok(ranking)
    }

    /// Create ranking from cached scores
    #[allow(clippy::unnecessary_wraps)]
    fn create_ranking_from_cache(
        &self,
        asset: &AssetData,
        cached_scores: &HashMap<String, f64>,
    ) -> Result<AssetRanking, MLError> {
        let composite_score = cached_scores.get("composite").cloned().unwrap_or(0.0);

        Ok(AssetRanking {
            asset_id: asset.asset_id.clone(),
            symbol: asset.symbol.clone().into(),
            liquidity_rank: 0,
            momentum_rank: 0,
            volatility_rank: 0,
            correlation_score: 0.5,
            composite_score,
            percentile_rank: 0.0,
            sector: asset.sector.clone(),
            market_cap_rank: 0,
            is_selected: false,
            selection_confidence: composite_score.min(1.0),
        })
    }

    /// Apply selection criteria to ranked assets
    #[allow(clippy::unnecessary_wraps)]
    fn apply_selection_criteria(
        &self,
        rankings: &[AssetRanking],
    ) -> Result<Vec<AssetRanking>, MLError> {
        let mut selected = Vec::new();
        let mut sector_counts: HashMap<String, usize> = HashMap::new();

        for ranking in rankings {
            // Check liquidity threshold
            if ranking.selection_confidence < self.criteria.min_liquidity_score {
                continue;
            }

            // Check sector limits
            let sector_count = sector_counts.get(&ranking.sector).cloned().unwrap_or(0);
            if let Some(&limit) = self.criteria.sector_limits.get(&ranking.sector) {
                if sector_count >= limit {
                    continue;
                }
            }

            // Check max assets limit
            if selected.len() >= self.criteria.max_assets {
                break;
            }

            let mut selected_ranking = ranking.clone();
            selected_ranking.is_selected = true;
            selected.push(selected_ranking);

            *sector_counts.entry(ranking.sector.clone()).or_insert(0) += 1;
        }

        Ok(selected)
    }

    /// Calculate performance metrics
    #[allow(clippy::unnecessary_wraps)]
    fn calculate_performance_metrics(&self) -> Result<UniversePerformanceMetrics, MLError> {
        let universe_assets: Vec<&AssetRanking> = self.current_universe.values().collect();

        let total_universe_size = universe_assets.len();

        let avg_liquidity_score = if !universe_assets.is_empty() {
            universe_assets
                .iter()
                .map(|a| a.selection_confidence)
                .sum::<f64>()
                / total_universe_size as f64
        } else {
            0.0
        };

        // Simplified metrics - in production would be more comprehensive
        Ok(UniversePerformanceMetrics {
            total_universe_size,
            avg_liquidity_score,
            avg_volatility: 0.15, // Production
            sector_diversification: self.calculate_sector_diversification(&universe_assets),
            correlation_efficiency: 0.7, // Production
            turnover_rate: 0.1,          // Production
            selection_latency_ms: 50,    // Production
        })
    }

    /// Calculate sector diversification score
    fn calculate_sector_diversification(&self, assets: &[&AssetRanking]) -> f64 {
        if assets.is_empty() {
            return 0.0;
        }

        let mut sector_counts: HashMap<String, usize> = HashMap::new();
        for asset in assets {
            *sector_counts.entry(asset.sector.clone()).or_insert(0) += 1;
        }

        let _num_sectors = sector_counts.len() as f64;
        let total_assets = assets.len() as f64;

        // Calculate Herfindahl-Hirschman Index for diversification
        let hhi: f64 = sector_counts
            .values()
            .map(|&count| {
                let proportion = count as f64 / total_assets;
                proportion * proportion
            })
            .sum();

        // Convert to diversification score (lower HHI = higher diversification)
        (1.0 - hhi).max(0.0)
    }

    /// Get current universe
    pub const fn get_current_universe(&self) -> &HashMap<Symbol, AssetRanking> {
        &self.current_universe
    }

    /// Get performance metrics
    pub fn get_performance_metrics(&self) -> Option<&UniversePerformanceMetrics> {
        self.performance_history.last()
    }

    /// Update liquidity scores for all tracked assets
    pub async fn update_liquidity_scores(&mut self) -> Result<(), MLError> {
        // Production implementation - updates liquidity scores in background
        tracing::debug!(
            "Updating liquidity scores for {} assets",
            self.current_universe.len()
        );
        Ok(())
    }

    /// Update momentum rankings for all tracked assets
    pub async fn update_momentum_rankings(&mut self) -> Result<(), MLError> {
        // Production implementation - updates momentum rankings in background
        tracing::debug!(
            "Updating momentum rankings for {} assets",
            self.current_universe.len()
        );
        Ok(())
    }

    /// Update volatility clustering for all tracked assets
    pub async fn update_volatility_clustering(&mut self) -> Result<(), MLError> {
        // Production implementation - updates volatility clusters in background
        tracing::debug!(
            "Updating volatility clustering for {} assets",
            self.current_universe.len()
        );
        Ok(())
    }

    /// Generate a universe update event
    pub fn generate_universe_update(&self) -> Result<UniverseUpdate, MLError> {
        let added_assets: Vec<Symbol> = self.current_universe.keys().cloned().collect();
        let removed_assets = Vec::new(); // Would be populated with rejected assets
        let ranking_changes = self.current_universe.clone();

        Ok(UniverseUpdate {
            added_assets,
            removed_assets,
            timestamp: SystemTime::now(),
            ranking_changes,
            selection_criteria_used: self.criteria.clone(),
            update_reason: UniverseUpdateReason::Scheduled,
            performance_metrics: self.performance_history.last().cloned().unwrap_or_default(),
        })
    }
}
