//! # Input Validation Module
//!
//! Comprehensive input validation for Foxhunt HFT Trading System
//! Prevents injection attacks, data corruption, and ensures financial data integrity

#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)]

use regex::Regex;
use rust_decimal::Decimal;
use std::collections::HashMap;
use thiserror::Error;

/// Maximum allowed string lengths to prevent `DoS` attacks
pub const MAX_SYMBOL_LENGTH: usize = 12;
/// MAX_ACCOUNT_ID_LENGTH
pub const MAX_ACCOUNT_ID_LENGTH: usize = 32;
/// MAX_DESCRIPTION_LENGTH
pub const MAX_DESCRIPTION_LENGTH: usize = 256;
/// MAX_METADATA_KEY_LENGTH
pub const MAX_METADATA_KEY_LENGTH: usize = 64;
/// MAX_METADATA_VALUE_LENGTH
pub const MAX_METADATA_VALUE_LENGTH: usize = 512;
/// MAX_METADATA_ENTRIES
pub const MAX_METADATA_ENTRIES: usize = 100;

/// Financial limits to prevent overflow and unrealistic values
pub const MAX_PRICE: f64 = 1_000_000.0;
/// MIN_PRICE
pub const MIN_PRICE: f64 = 0.000_001;
/// MAX_QUANTITY
pub const MAX_QUANTITY: f64 = 1_000_000_000.0;
/// MIN_QUANTITY
pub const MIN_QUANTITY: f64 = 0.000_001;
/// MAX_LEVERAGE
pub const MAX_LEVERAGE: f64 = 1000.0;
/// MIN_LEVERAGE
pub const MIN_LEVERAGE: f64 = 0.1;

/// Validation errors with specific context
#[derive(Error, Debug, Clone, PartialEq)]
/// ValidationError
///
/// Auto-generated documentation placeholder - enhance with specifics
pub enum ValidationError {
    #[error("Invalid symbol format: {symbol}. Must be 1-{max_len} alphanumeric characters")]
    InvalidSymbol { symbol: String, max_len: usize },

    #[error("Invalid account ID: {account_id}. Must be 1-{max_len} characters")]
    InvalidAccountId { account_id: String, max_len: usize },

    #[error("Price {price} is out of valid range [{min}, {max}]")]
    InvalidPrice { price: f64, min: f64, max: f64 },

    #[error("Quantity {quantity} is out of valid range [{min}, {max}]")]
    InvalidQuantity { quantity: f64, min: f64, max: f64 },

    #[error("Leverage {leverage} is out of valid range [{min}, {max}]")]
    InvalidLeverage { leverage: f64, min: f64, max: f64 },

    #[error("String too long: {len} characters, maximum {max_len}")]
    StringTooLong { len: usize, max_len: usize },

    #[error("Invalid characters detected in input: {input}")]
    InvalidCharacters { input: String },

    #[error("Metadata validation failed: {reason}")]
    InvalidMetadata { reason: String },

    #[error("SQL injection attempt detected in: {input}")]
    SqlInjectionAttempt { input: String },

    #[error("XSS attempt detected in: {input}")]
    XssAttempt { input: String },

    #[error("Required field is missing: {field}")]
    MissingRequiredField { field: String },

    #[error("Invalid enum value: {value} for field {field}")]
    InvalidEnumValue { value: String, field: String },
}

/// `Result` type for validation operations
pub type ValidationResult<T> = Result<T, ValidationError>;

/// Input sanitization and validation utilities
#[derive(Debug)]
/// InputValidator
///
/// Auto-generated documentation placeholder - enhance with specifics
pub struct InputValidator;

impl InputValidator {
    /// Validates and sanitizes a trading symbol
    pub fn validate_symbol(symbol: &str) -> ValidationResult<String> {
        if symbol.is_empty() || symbol.len() > MAX_SYMBOL_LENGTH {
            return Err(ValidationError::InvalidSymbol {
                symbol: symbol.to_owned(),
                max_len: MAX_SYMBOL_LENGTH,
            });
        }

        // Allow only alphanumeric characters, dots, and dashes
        let symbol_regex =
            Regex::new("^[A-Za-z0-9.-]+$").map_err(|e| ValidationError::InvalidCharacters {
                input: format!("Invalid regex pattern: {e}"),
            })?;
        if !symbol_regex.is_match(symbol) {
            return Err(ValidationError::InvalidCharacters {
                input: symbol.to_owned(),
            });
        }

        // Check for potential injection patterns
        Self::check_injection_patterns(symbol)?;

        Ok(symbol.to_uppercase())
    }

    /// Validates account ID format
    pub fn validate_account_id(account_id: &str) -> ValidationResult<String> {
        if account_id.is_empty() || account_id.len() > MAX_ACCOUNT_ID_LENGTH {
            return Err(ValidationError::InvalidAccountId {
                account_id: account_id.to_owned(),
                max_len: MAX_ACCOUNT_ID_LENGTH,
            });
        }

        // Allow alphanumeric characters, underscores, and dashes
        let account_regex =
            Regex::new("^[A-Za-z0-9_-]+$").map_err(|e| ValidationError::InvalidCharacters {
                input: format!("Invalid account regex pattern: {e}"),
            })?;
        if !account_regex.is_match(account_id) {
            return Err(ValidationError::InvalidCharacters {
                input: account_id.to_owned(),
            });
        }

        Self::check_injection_patterns(account_id)?;
        Ok(account_id.to_owned())
    }

    /// Validates financial price values
    pub fn validate_price(price: f64) -> ValidationResult<Decimal> {
        if price.is_nan() || price.is_infinite() {
            return Err(ValidationError::InvalidPrice {
                price,
                min: MIN_PRICE,
                max: MAX_PRICE,
            });
        }

        if !(MIN_PRICE..=MAX_PRICE).contains(&price) {
            return Err(ValidationError::InvalidPrice {
                price,
                min: MIN_PRICE,
                max: MAX_PRICE,
            });
        }

        Decimal::try_from(price).map_err(|_| ValidationError::InvalidPrice {
            price,
            min: MIN_PRICE,
            max: MAX_PRICE,
        })
    }

    /// Validates quantity values
    pub fn validate_quantity(quantity: f64) -> ValidationResult<Decimal> {
        if quantity.is_nan() || quantity.is_infinite() || quantity <= 0.0 {
            return Err(ValidationError::InvalidQuantity {
                quantity,
                min: MIN_QUANTITY,
                max: MAX_QUANTITY,
            });
        }

        if !(MIN_QUANTITY..=MAX_QUANTITY).contains(&quantity) {
            return Err(ValidationError::InvalidQuantity {
                quantity,
                min: MIN_QUANTITY,
                max: MAX_QUANTITY,
            });
        }

        Decimal::try_from(quantity).map_err(|_| ValidationError::InvalidQuantity {
            quantity,
            min: MIN_QUANTITY,
            max: MAX_QUANTITY,
        })
    }

    /// Validates leverage values
    pub fn validate_leverage(leverage: f64) -> ValidationResult<f64> {
        if leverage.is_nan() || leverage.is_infinite() || leverage <= 0.0 {
            return Err(ValidationError::InvalidLeverage {
                leverage,
                min: MIN_LEVERAGE,
                max: MAX_LEVERAGE,
            });
        }

        if !(MIN_LEVERAGE..=MAX_LEVERAGE).contains(&leverage) {
            return Err(ValidationError::InvalidLeverage {
                leverage,
                min: MIN_LEVERAGE,
                max: MAX_LEVERAGE,
            });
        }

        // Ok variant
        Ok(leverage)
    }

    /// Validates and sanitizes general text input
    pub fn validate_text(
        text: &str,
        max_length: usize,
        _field_name: &str,
    ) -> ValidationResult<String> {
        if text.len() > max_length {
            return Err(ValidationError::StringTooLong {
                len: text.len(),
                max_len: max_length,
            });
        }

        Self::check_injection_patterns(text)?;
        Self::check_xss_patterns(text)?;

        // Remove null bytes and control characters
        let sanitized = text
            .chars()
            .filter(|&c| c >= ' ' || c == '\t' || c == '\n' || c == '\r')
            .collect::<String>();

        // Ok variant
        Ok(sanitized)
    }

    /// Validates metadata `HashMap`
    pub fn validate_metadata(
        metadata: &HashMap<String, String>,
    ) -> ValidationResult<HashMap<String, String>> {
        if metadata.len() > MAX_METADATA_ENTRIES {
            return Err(ValidationError::InvalidMetadata {
                reason: format!(
                    "Too many metadata entries: {} > {}",
                    metadata.len(),
                    MAX_METADATA_ENTRIES
                ),
            });
        }

        let mut validated = HashMap::new();

        for (key, value) in metadata {
            // Validate key
            if key.len() > MAX_METADATA_KEY_LENGTH {
                return Err(ValidationError::StringTooLong {
                    len: key.len(),
                    max_len: MAX_METADATA_KEY_LENGTH,
                });
            }

            let validated_key = Self::validate_text(key, MAX_METADATA_KEY_LENGTH, "metadata_key")?;

            // Validate value
            if value.len() > MAX_METADATA_VALUE_LENGTH {
                return Err(ValidationError::StringTooLong {
                    len: value.len(),
                    max_len: MAX_METADATA_VALUE_LENGTH,
                });
            }

            let validated_value =
                Self::validate_text(value, MAX_METADATA_VALUE_LENGTH, "metadata_value")?;

            validated.insert(validated_key, validated_value);
        }

        // Ok variant
        Ok(validated)
    }

    /// Checks for SQL injection patterns
    fn check_injection_patterns(input: &str) -> ValidationResult<()> {
        let injection_patterns = [
            r"(?i)(\bunion\b|\bselect\b|\binsert\b|\bupdate\b|\bdelete\b|\bdrop\b|\balter\b)",
            r"(?i)(\bor\b\s+\d+=\d+|\band\b\s+\d+=\d+)",
            r"(?i)(--|#|/\*|\*/)",
            r"(?i)(\bexec\b|\bexecute\b|\bsp_\w+)",
            r#"['";]"#,
            r"(?i)(\bscript\b|\bjavascript\b|\bvbscript\b)",
        ];
        for pattern in &injection_patterns {
            let regex = Regex::new(pattern).map_err(|e| ValidationError::SqlInjectionAttempt {
                input: format!("Invalid SQL injection regex pattern: {e}"),
            })?;
            if regex.is_match(input) {
                return Err(ValidationError::SqlInjectionAttempt {
                    input: input.to_owned(),
                });
            }
        }

        Ok(())
    }

    /// Checks for XSS patterns
    fn check_xss_patterns(input: &str) -> ValidationResult<()> {
        let xss_patterns = [
            "(?i)<script",
            "(?i)<iframe",
            "(?i)<object",
            "(?i)<embed",
            "(?i)<link",
            "(?i)<meta",
            "(?i)javascript:",
            "(?i)vbscript:",
            "(?i)data:",
            r"(?i)on\w+\s*=",
        ];

        for pattern in &xss_patterns {
            let regex = Regex::new(pattern).map_err(|e| ValidationError::XssAttempt {
                input: format!("Invalid XSS regex pattern: {e}"),
            })?;
            if regex.is_match(input) {
                return Err(ValidationError::XssAttempt {
                    input: input.to_owned(),
                });
            }
        }

        Ok(())
    }

    /// Validates required fields are present
    pub fn require_field<T>(field: Option<T>, field_name: &str) -> ValidationResult<T> {
        field.ok_or_else(|| ValidationError::MissingRequiredField {
            field: field_name.to_owned(),
        })
    }

    /// Validates enum values
    pub fn validate_enum_value<T: std::fmt::Display>(
        value: &str,
        valid_values: &[&str],
        field_name: &str,
    ) -> ValidationResult<()> {
        if !valid_values.contains(&value) {
            return Err(ValidationError::InvalidEnumValue {
                value: value.to_owned(),
                field: field_name.to_owned(),
            });
        }
        Ok(())
    }
}

/// Validation trait for types that can validate themselves
pub trait Validate {
    fn validate(&self) -> ValidationResult<()>;
}

/// Macro for quick validation of multiple fields
#[macro_export]
macro_rules! validate_fields {
    ($($field:expr => $validator:expr),* $(,)?) => {
        {
            let mut errors = ::std::vec::Vec::new();
            $(
                if let ::std::result::Result::Err(e) = $validator {
                    errors.push(e);
                }
            )*
            if let Some(first_error) = errors.into_iter().next() {
                return ::std::result::Result::Err(first_error);
            }
        }
    };
}

#[cfg(test)]
#[allow(clippy::assertions_on_result_states)]
mod tests {
    use super::*;
    use crate::types::test_utils::test_symbols::*;

    #[test]
    fn test_symbol_validation() {
        assert!(InputValidator::validate_symbol(TEST_SYMBOL_1).is_ok());
        assert!(InputValidator::validate_symbol("EUR-USD").is_ok());
        assert!(InputValidator::validate_symbol("BTC.USD").is_ok());

        assert!(InputValidator::validate_symbol("").is_err());
        assert!(InputValidator::validate_symbol("VERYLONGSYMBOLNAME").is_err());
        assert!(InputValidator::validate_symbol(&format!(
            "{}'; DROP TABLE orders; --",
            TEST_SYMBOL_1
        ))
        .is_err());
    }

    #[test]
    fn test_price_validation() {
        assert!(InputValidator::validate_price(100.50).is_ok());
        assert!(InputValidator::validate_price(0.000_001).is_ok());

        assert!(InputValidator::validate_price(-1.0).is_err());
        assert!(InputValidator::validate_price(f64::NAN).is_err());
        assert!(InputValidator::validate_price(f64::INFINITY).is_err());
        assert!(InputValidator::validate_price(2_000_000.0).is_err());
    }

    #[test]
    fn test_injection_detection() {
        assert!(InputValidator::validate_text("'; DROP TABLE users; --", 100, "test").is_err());
        assert!(
            InputValidator::validate_text("<script>alert('xss')</script>", 100, "test").is_err()
        );
        assert!(InputValidator::validate_text("normal text", 100, "test").is_ok());
    }
}
