//! Asset Parser & Validation Module
//!
//! Provides parsing and validation for trading assets (futures and equities).
//! Supports comma-separated multi-asset input with automatic normalization,
//! deduplication, and comprehensive error handling.
//!
//! # Examples
//!
//! ```
//! use ml_training_service::asset_parser::{Asset, AssetParser};
//!
//! // Parse single future
//! let assets = AssetParser::parse("ES.FUT").unwrap();
//! assert_eq!(assets.len(), 1);
//!
//! // Parse multiple mixed assets
//! let assets = AssetParser::parse("ES.FUT,AAPL,NQ.FUT").unwrap();
//! assert_eq!(assets.len(), 3);
//!
//! // Case normalization
//! let assets = AssetParser::parse("es.fut").unwrap();
//! // Returns Asset::Future { symbol: "ES.FUT" }
//! ```

use anyhow::{bail, Context, Result};
use once_cell::sync::Lazy;
use regex::Regex;
use std::collections::HashSet;
use std::fmt;

// ============================================================================
// COMPILED REGEX PATTERNS (static, compiled once)
// ============================================================================

/// Futures pattern: 1-4 alphanumeric chars followed by `.FUT`
static FUTURES_RE: Lazy<Regex> = Lazy::new(|| {
    Regex::new(r"^[A-Z0-9]{1,4}\.FUT$").unwrap_or_else(|e| {
        eprintln!("FATAL: Failed to compile futures regex: {e}");
        std::process::abort()
    })
});

/// Equity pattern: 3-5 uppercase letters (min 3 to avoid futures ambiguity)
static EQUITY_RE: Lazy<Regex> = Lazy::new(|| {
    Regex::new(r"^[A-Z]{3,5}$").unwrap_or_else(|e| {
        eprintln!("FATAL: Failed to compile equity regex: {e}");
        std::process::abort()
    })
});

/// Short symbol pattern: 1-2 alphanumeric chars (likely missing `.FUT`)
static SHORT_SYMBOL_RE: Lazy<Regex> = Lazy::new(|| {
    Regex::new(r"^[A-Z0-9]{1,2}$").unwrap_or_else(|e| {
        eprintln!("FATAL: Failed to compile short symbol regex: {e}");
        std::process::abort()
    })
});

// ============================================================================
// ASSET TYPE ENUM
// ============================================================================

/// Represents a validated trading asset
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Asset {
    /// Futures contract (e.g., ES.FUT, NQ.FUT, 6E.FUT)
    Future { symbol: String },
    /// Equity/stock (e.g., AAPL, MSFT, GOOGL)
    Equity { symbol: String },
}

impl fmt::Display for Asset {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Asset::Future { symbol } => write!(f, "{}", symbol),
            Asset::Equity { symbol } => write!(f, "{}", symbol),
        }
    }
}

impl Asset {
    /// Get the symbol string for this asset
    pub fn symbol(&self) -> &str {
        match self {
            Asset::Future { symbol } => symbol,
            Asset::Equity { symbol } => symbol,
        }
    }

    /// Check if this is a futures contract
    pub fn is_future(&self) -> bool {
        matches!(self, Asset::Future { .. })
    }

    /// Check if this is an equity
    pub fn is_equity(&self) -> bool {
        matches!(self, Asset::Equity { .. })
    }
}

// ============================================================================
// ASSET PARSER
// ============================================================================

/// Parser for trading asset symbols with validation
pub struct AssetParser;

impl AssetParser {
    /// Parse a comma-separated list of assets
    ///
    /// # Features
    /// - Supports futures: `ES.FUT`, `NQ.FUT`, `6E.FUT` (1-4 alphanumeric + `.FUT`)
    /// - Supports equities: `AAPL`, `MSFT`, `GOOGL` (1-5 uppercase letters)
    /// - Automatic case normalization (lowercase → uppercase)
    /// - Whitespace trimming
    /// - Automatic deduplication
    ///
    /// # Arguments
    /// * `input` - Comma-separated asset list (e.g., "ES.FUT,AAPL,NQ.FUT")
    ///
    /// # Returns
    /// * `Ok(Vec<Asset>)` - Parsed and validated assets
    /// * `Err(anyhow::Error)` - Parse or validation error
    ///
    /// # Performance
    /// Target: <1μs per asset
    ///
    /// # Examples
    /// ```
    /// use ml_training_service::asset_parser::AssetParser;
    ///
    /// let assets = AssetParser::parse("ES.FUT,AAPL").unwrap();
    /// assert_eq!(assets.len(), 2);
    /// ```
    pub fn parse(input: &str) -> Result<Vec<Asset>> {
        // Trim and check for empty input
        let trimmed = input.trim();
        if trimmed.is_empty() {
            bail!("Asset list cannot be empty");
        }

        // Split by comma, normalize, and deduplicate
        let raw_assets: HashSet<String> = trimmed
            .split(',')
            .map(|s| s.trim().to_uppercase())
            .filter(|s| !s.is_empty())
            .collect();

        if raw_assets.is_empty() {
            bail!("Asset list contains no valid assets (only whitespace/commas)");
        }

        // Parse each unique asset
        let mut assets = Vec::with_capacity(raw_assets.len());
        for asset_str in raw_assets {
            let asset = Self::parse_single(&asset_str)
                .context("Invalid asset format")?;
            assets.push(asset);
        }

        Ok(assets)
    }

    /// Parse a single asset string (internal helper)
    fn parse_single(input: &str) -> Result<Asset> {
        // Try futures first (more specific pattern)
        if FUTURES_RE.is_match(input) {
            return Ok(Asset::Future {
                symbol: input.to_string(),
            });
        }

        // Try equity (3-5 letters to avoid ambiguity with futures symbols like ES, NQ)
        if EQUITY_RE.is_match(input) {
            return Ok(Asset::Equity {
                symbol: input.to_string(),
            });
        }

        // Special error message for 1-2 character symbols (likely missing .FUT)
        if SHORT_SYMBOL_RE.is_match(input) {
            bail!(
                "Invalid asset format: '{}' - This looks like a futures symbol. Did you mean '{}.FUT'?\n\n\
                Expected formats:\n\
                  - Futures: SYMBOL.FUT (e.g., ES.FUT, NQ.FUT)\n\
                  - Equities: 3-5 letter tickers (e.g., AAPL, MSFT, GOOGL)",
                input,
                input
            );
        }

        // Neither pattern matched - provide helpful error
        bail!(
            "Invalid asset format: '{}'\n\
            Expected formats:\n\
              - Futures: SYMBOL.FUT (e.g., ES.FUT, NQ.FUT, 6E.FUT, ZN.FUT)\n\
                Pattern: ^[A-Z0-9]{{1,4}}\\.FUT$\n\
              - Equities: 3-5 letter tickers (e.g., AAPL, MSFT, GOOGL)\n\
                Pattern: ^[A-Z]{{3,5}}$",
            input
        );
    }

    /// Validate a futures contract symbol
    ///
    /// # Format
    /// - Pattern: `{SYMBOL}.FUT` where SYMBOL is 1-4 alphanumeric characters
    /// - Regex: `^[A-Z0-9]{1,4}\.FUT$`
    /// - Examples: ES.FUT, NQ.FUT, 6E.FUT, ZN.FUT
    ///
    /// # Arguments
    /// * `symbol` - The futures symbol to validate (already uppercased)
    ///
    /// # Returns
    /// * `Ok(Asset::Future)` - Valid futures contract
    /// * `Err(anyhow::Error)` - Invalid format
    pub fn validate_future(symbol: &str) -> Result<Asset> {
        if FUTURES_RE.is_match(symbol) {
            Ok(Asset::Future {
                symbol: symbol.to_string(),
            })
        } else {
            bail!(
                "Invalid asset format: '{}'\n\
                Expected futures format: SYMBOL.FUT\n\
                  - SYMBOL: 1-4 alphanumeric characters (A-Z, 0-9)\n\
                  - Examples: ES.FUT, NQ.FUT, 6E.FUT, ZN.FUT\n\
                Valid patterns:\n\
                  - Futures: ^[A-Z0-9]{{1,4}}\\.FUT$\n\
                  - Equities: ^[A-Z]{{1,5}}$",
                symbol
            );
        }
    }

    /// Validate an equity symbol
    ///
    /// # Format
    /// - Pattern: 3-5 uppercase letters (min 3 to avoid futures ambiguity)
    /// - Regex: `^[A-Z]{3,5}$`
    /// - Examples: AAPL, MSFT, GOOGL, TSLA
    ///
    /// # Note
    /// 1-2 character symbols are rejected to avoid ambiguity with futures symbols
    /// like ES, NQ, CL, etc. Use .FUT suffix for futures contracts.
    ///
    /// # Arguments
    /// * `symbol` - The equity symbol to validate (already uppercased)
    ///
    /// # Returns
    /// * `Ok(Asset::Equity)` - Valid equity symbol
    /// * `Err(anyhow::Error)` - Invalid format
    pub fn validate_equity(symbol: &str) -> Result<Asset> {
        if EQUITY_RE.is_match(symbol) {
            Ok(Asset::Equity {
                symbol: symbol.to_string(),
            })
        } else {
            bail!(
                "Invalid asset format: '{}'\n\
                Expected equity format: TICKER\n\
                  - TICKER: 3-5 uppercase letters (A-Z only)\n\
                  - Examples: AAPL, MSFT, GOOGL, TSLA\n\
                Valid patterns:\n\
                  - Futures: ^[A-Z0-9]{{1,4}}\\.FUT$\n\
                  - Equities: ^[A-Z]{{3,5}}$",
                symbol
            );
        }
    }
}

// ============================================================================
// UNIT TESTS
// ============================================================================

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

    #[test]
    fn test_asset_display() {
        let future = Asset::Future {
            symbol: "ES.FUT".to_string(),
        };
        assert_eq!(format!("{}", future), "ES.FUT");

        let equity = Asset::Equity {
            symbol: "AAPL".to_string(),
        };
        assert_eq!(format!("{}", equity), "AAPL");
    }

    #[test]
    fn test_asset_helpers() {
        let future = Asset::Future {
            symbol: "ES.FUT".to_string(),
        };
        assert!(future.is_future());
        assert!(!future.is_equity());
        assert_eq!(future.symbol(), "ES.FUT");

        let equity = Asset::Equity {
            symbol: "AAPL".to_string(),
        };
        assert!(equity.is_equity());
        assert!(!equity.is_future());
        assert_eq!(equity.symbol(), "AAPL");
    }

    #[test]
    fn test_parse_basic_futures() {
        let result = AssetParser::parse("ES.FUT").unwrap();
        assert_eq!(result.len(), 1);
        assert!(result[0].is_future());
    }

    #[test]
    fn test_parse_basic_equities() {
        let result = AssetParser::parse("AAPL").unwrap();
        assert_eq!(result.len(), 1);
        assert!(result[0].is_equity());
    }

    #[test]
    fn test_case_normalization() {
        let result = AssetParser::parse("es.fut").unwrap();
        assert_eq!(result[0].symbol(), "ES.FUT");

        let result = AssetParser::parse("aapl").unwrap();
        assert_eq!(result[0].symbol(), "AAPL");
    }

    #[test]
    fn test_deduplication() {
        let result = AssetParser::parse("ES.FUT,ES.FUT,es.fut").unwrap();
        assert_eq!(result.len(), 1);
    }

    #[test]
    fn test_whitespace_handling() {
        let result = AssetParser::parse(" ES.FUT , AAPL ").unwrap();
        assert_eq!(result.len(), 2);
    }

    #[test]
    fn test_empty_input_error() {
        assert!(AssetParser::parse("").is_err());
        assert!(AssetParser::parse("   ").is_err());
        assert!(AssetParser::parse(",,,").is_err());
    }

    #[test]
    fn test_invalid_format_error() {
        assert!(AssetParser::parse("INVALID_FORMAT").is_err());
        assert!(AssetParser::parse("ES").is_err());
        assert!(AssetParser::parse("TOOLONG").is_err());
    }
}
