//! Comprehensive tests for configuration schemas module.
//!
//! Tests for S3Config, ConfigSchema, and AssetClassificationSchema structures
//! including validation, serialization, classification logic, and edge cases.

use chrono::Utc;
use config::schemas::{AssetClassificationSchema, ConfigSchema, S3Config};
use std::time::Duration;
use uuid::Uuid;

// ============================================================================
// S3Config Tests (12 tests)
// ============================================================================

#[test]
fn test_s3config_default_values() {
    let config = S3Config::default();

    assert_eq!(config.bucket_name, "foxhunt-models");
    assert_eq!(config.region, "us-east-1");
    assert!(config.access_key_id.is_none());
    assert!(config.secret_access_key.is_none());
    assert!(config.session_token.is_none());
    assert!(config.endpoint_url.is_none());
    assert!(!config.force_path_style);
    assert_eq!(config.timeout, Duration::from_secs(30));
    assert_eq!(config.max_retry_attempts, 3);
    assert!(config.use_ssl);
}

#[test]
fn test_s3config_validate_success() {
    let config = S3Config {
        bucket_name: "test-bucket".to_string(),
        region: "us-west-2".to_string(),
        ..Default::default()
    };

    let result = config.validate();
    assert!(result.is_ok());
}

#[test]
fn test_s3config_validate_empty_bucket_name() {
    let config = S3Config {
        bucket_name: "".to_string(),
        region: "us-west-2".to_string(),
        ..Default::default()
    };

    let result = config.validate();
    assert!(result.is_err());
    assert_eq!(result.unwrap_err(), "S3 bucket name cannot be empty");
}

#[test]
fn test_s3config_validate_empty_region() {
    let config = S3Config {
        bucket_name: "test-bucket".to_string(),
        region: "".to_string(),
        ..Default::default()
    };

    let result = config.validate();
    assert!(result.is_err());
    assert_eq!(result.unwrap_err(), "S3 region cannot be empty");
}

#[test]
fn test_s3config_with_credentials() {
    let config = S3Config {
        bucket_name: "secure-bucket".to_string(),
        region: "eu-west-1".to_string(),
        access_key_id: Some("AKIAIOSFODNN7EXAMPLE".to_string()),
        secret_access_key: Some("wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY".to_string()),
        session_token: Some("session-token-123".to_string()),
        ..Default::default()
    };

    assert!(config.validate().is_ok());
    assert_eq!(config.access_key_id.unwrap(), "AKIAIOSFODNN7EXAMPLE");
    assert_eq!(
        config.secret_access_key.unwrap(),
        "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
    );
    assert_eq!(config.session_token.unwrap(), "session-token-123");
}

#[test]
fn test_s3config_minio_endpoint() {
    let config = S3Config {
        bucket_name: "minio-bucket".to_string(),
        region: "us-east-1".to_string(),
        endpoint_url: Some("http://localhost:9000".to_string()),
        force_path_style: true,
        use_ssl: false,
        ..Default::default()
    };

    assert!(config.validate().is_ok());
    assert_eq!(config.endpoint_url.unwrap(), "http://localhost:9000");
    assert!(config.force_path_style);
    assert!(!config.use_ssl);
}

#[test]
fn test_s3config_custom_timeout_and_retries() {
    let config = S3Config {
        bucket_name: "timeout-test".to_string(),
        region: "ap-southeast-2".to_string(),
        timeout: Duration::from_secs(60),
        max_retry_attempts: 5,
        ..Default::default()
    };

    assert!(config.validate().is_ok());
    assert_eq!(config.timeout, Duration::from_secs(60));
    assert_eq!(config.max_retry_attempts, 5);
}

#[test]
fn test_s3config_serialization() {
    let config = S3Config {
        bucket_name: "serde-test".to_string(),
        region: "us-east-1".to_string(),
        access_key_id: Some("key123".to_string()),
        ..Default::default()
    };

    let json = serde_json::to_string(&config).unwrap();
    assert!(json.contains("serde-test"));
    assert!(json.contains("us-east-1"));
    assert!(json.contains("key123"));

    let deserialized: S3Config = serde_json::from_str(&json).unwrap();
    assert_eq!(deserialized.bucket_name, "serde-test");
    assert_eq!(deserialized.region, "us-east-1");
    assert_eq!(deserialized.access_key_id.unwrap(), "key123");
}

#[test]
fn test_s3config_clone() {
    let config = S3Config {
        bucket_name: "clone-test".to_string(),
        region: "us-west-1".to_string(),
        max_retry_attempts: 10,
        ..Default::default()
    };

    let cloned = config;
    assert_eq!(cloned.bucket_name, "clone-test");
    assert_eq!(cloned.region, "us-west-1");
    assert_eq!(cloned.max_retry_attempts, 10);
}

#[test]
fn test_s3config_debug_format() {
    let config = S3Config {
        bucket_name: "debug-test".to_string(),
        region: "eu-central-1".to_string(),
        ..Default::default()
    };

    let debug_str = format!("{:?}", config);
    assert!(debug_str.contains("debug-test"));
    assert!(debug_str.contains("eu-central-1"));
}

#[test]
fn test_s3config_edge_case_zero_timeout() {
    let config = S3Config {
        bucket_name: "zero-timeout".to_string(),
        region: "us-east-1".to_string(),
        timeout: Duration::from_secs(0),
        max_retry_attempts: 0,
        ..Default::default()
    };

    assert!(config.validate().is_ok());
    assert_eq!(config.timeout, Duration::from_secs(0));
    assert_eq!(config.max_retry_attempts, 0);
}

#[test]
fn test_s3config_edge_case_very_long_bucket_name() {
    let long_name = "a".repeat(255);
    let config = S3Config {
        bucket_name: long_name,
        region: "us-east-1".to_string(),
        ..Default::default()
    };

    assert!(config.validate().is_ok());
    assert_eq!(config.bucket_name.len(), 255);
}

// ============================================================================
// ConfigSchema Tests (7 tests)
// ============================================================================

#[test]
fn test_config_schema_creation() {
    let schema = ConfigSchema {
        id: Uuid::new_v4(),
        version: "1.0.0".to_string(),
        created_at: Utc::now(),
        updated_at: Utc::now(),
    };

    assert!(!schema.id.to_string().is_empty());
    assert_eq!(schema.version, "1.0.0");
    assert!(schema.created_at <= schema.updated_at);
}

#[test]
fn test_config_schema_semantic_versioning() {
    let versions = vec!["1.0.0", "1.2.3", "2.0.0-beta", "3.1.4-alpha.1"];

    for version in versions {
        let schema = ConfigSchema {
            id: Uuid::new_v4(),
            version: version.to_string(),
            created_at: Utc::now(),
            updated_at: Utc::now(),
        };
        assert_eq!(schema.version, version);
    }
}

#[test]
fn test_config_schema_uuid_uniqueness() {
    let schema1 = ConfigSchema {
        id: Uuid::new_v4(),
        version: "1.0.0".to_string(),
        created_at: Utc::now(),
        updated_at: Utc::now(),
    };

    let schema2 = ConfigSchema {
        id: Uuid::new_v4(),
        version: "1.0.0".to_string(),
        created_at: Utc::now(),
        updated_at: Utc::now(),
    };

    assert_ne!(schema1.id, schema2.id);
}

#[test]
fn test_config_schema_timestamp_ordering() {
    let now = Utc::now();
    let schema = ConfigSchema {
        id: Uuid::new_v4(),
        version: "1.0.0".to_string(),
        created_at: now,
        updated_at: now,
    };

    assert!(schema.created_at <= schema.updated_at);
}

#[test]
fn test_config_schema_serialization() {
    let schema = ConfigSchema {
        id: Uuid::new_v4(),
        version: "2.3.4".to_string(),
        created_at: Utc::now(),
        updated_at: Utc::now(),
    };

    let json = serde_json::to_string(&schema).unwrap();
    assert!(json.contains("2.3.4"));

    let deserialized: ConfigSchema = serde_json::from_str(&json).unwrap();
    assert_eq!(deserialized.id, schema.id);
    assert_eq!(deserialized.version, "2.3.4");
}

#[test]
fn test_config_schema_clone() {
    let schema = ConfigSchema {
        id: Uuid::new_v4(),
        version: "1.5.0".to_string(),
        created_at: Utc::now(),
        updated_at: Utc::now(),
    };

    let cloned = schema.clone();
    assert_eq!(cloned.id, schema.id);
    assert_eq!(cloned.version, schema.version);
    assert_eq!(cloned.created_at, schema.created_at);
}

#[test]
fn test_config_schema_debug_format() {
    let schema = ConfigSchema {
        id: Uuid::new_v4(),
        version: "3.0.0".to_string(),
        created_at: Utc::now(),
        updated_at: Utc::now(),
    };

    let debug_str = format!("{:?}", schema);
    assert!(debug_str.contains("3.0.0"));
    assert!(debug_str.contains(&schema.id.to_string()));
}

// ============================================================================
// AssetClassificationSchema Tests (16 tests)
// ============================================================================

#[test]
fn test_asset_classification_new() {
    let config = AssetClassificationSchema::new();

    assert_eq!(config.asset_type_rules.len(), 5);
    assert_eq!(config.default_sectors.len(), 5);
    assert_eq!(config.currency_patterns.len(), 5);
    assert_eq!(config.crypto_patterns.len(), 3);
}

#[test]
fn test_asset_classification_default() {
    let config = AssetClassificationSchema::default();

    assert!(config.asset_type_rules.contains_key("EQUITY"));
    assert!(config.asset_type_rules.contains_key("FOREX"));
    assert!(config.asset_type_rules.contains_key("CRYPTO"));
    assert_eq!(config.asset_type_rules["EQUITY"], "Equity");
    assert_eq!(config.asset_type_rules["FOREX"], "Currencies");
}

#[test]
fn test_asset_classification_asset_type_rules() {
    let config = AssetClassificationSchema::new();

    assert_eq!(config.asset_type_rules.get("EQUITY").unwrap(), "Equity");
    assert_eq!(config.asset_type_rules.get("FOREX").unwrap(), "Currencies");
    assert_eq!(
        config.asset_type_rules.get("CRYPTO").unwrap(),
        "Cryptocurrency"
    );
    assert_eq!(
        config.asset_type_rules.get("COMMODITY").unwrap(),
        "Commodities"
    );
    assert_eq!(config.asset_type_rules.get("BOND").unwrap(), "Fixed Income");
}

#[test]
fn test_asset_classification_classify_by_asset_type() {
    let config = AssetClassificationSchema::new();

    let sector = config.classify_sector("AAPL", Some("EQUITY"));
    assert_eq!(sector, "Equity");

    let sector = config.classify_sector("EURUSD", Some("FOREX"));
    assert_eq!(sector, "Currencies");

    let sector = config.classify_sector("BTC", Some("CRYPTO"));
    assert_eq!(sector, "Cryptocurrency");
}

#[test]
fn test_asset_classification_classify_currency_patterns() {
    let config = AssetClassificationSchema::new();

    // Test standard 6-character currency pair
    let sector = config.classify_sector("EURUSD", None);
    assert_eq!(sector, "Currencies");

    let sector = config.classify_sector("GBPJPY", None);
    assert_eq!(sector, "Currencies");

    // Test USD pattern
    let sector = config.classify_sector("USD/EUR", None);
    assert_eq!(sector, "Currencies");
}

#[test]
fn test_asset_classification_classify_crypto_patterns() {
    let config = AssetClassificationSchema::new();

    // Test crypto patterns that don't match any currency pattern
    // Currency patterns check for: ^[A-Z]{3}[A-Z]{3}$ OR .*USD/EUR/GBP/JPY.*

    // CRYPTO_INDEX: 12 chars, no USD/EUR/GBP/JPY, contains CRYPTO
    let sector = config.classify_sector("CRYPTO_INDEX", None);
    assert_eq!(sector, "Cryptocurrency");

    // BTC_PERP: 8 chars, no USD/EUR/GBP/JPY, contains BTC
    let sector = config.classify_sector("BTC_PERP", None);
    assert_eq!(sector, "Cryptocurrency");

    // ETH_FUTURE: 10 chars, no USD/EUR/GBP/JPY, contains ETH
    let sector = config.classify_sector("ETH_FUTURE", None);
    assert_eq!(sector, "Cryptocurrency");
}

#[test]
fn test_asset_classification_default_classification() {
    let config = AssetClassificationSchema::new();

    // Unknown instruments should return "Other"
    let sector = config.classify_sector("AAPL", None);
    assert_eq!(sector, "Other");

    let sector = config.classify_sector("TSLA", None);
    assert_eq!(sector, "Other");
}

#[test]
fn test_asset_classification_priority_asset_type_over_pattern() {
    let config = AssetClassificationSchema::new();

    // Even though "BTCUSD" matches crypto pattern, explicit asset type wins
    let sector = config.classify_sector("BTCUSD", Some("FOREX"));
    assert_eq!(sector, "Currencies");
}

#[test]
fn test_asset_classification_currency_pattern_matching() {
    let config = AssetClassificationSchema::new();

    // Test various currency patterns
    let instruments = vec![
        ("EURUSD", "Currencies"),
        ("USD/JPY", "Currencies"),
        ("EUR-USD", "Currencies"),
        ("GBP_USD", "Currencies"),
    ];

    for (instrument, expected) in instruments {
        let sector = config.classify_sector(instrument, None);
        assert_eq!(sector, expected, "Failed for instrument: {}", instrument);
    }
}

#[test]
fn test_asset_classification_crypto_pattern_matching() {
    let config = AssetClassificationSchema::new();

    // Test crypto patterns that don't match currency patterns
    // Currency patterns: ^[A-Z]{3}[A-Z]{3}$ (exactly 6 uppercase) or .*USD/EUR/GBP/JPY.*
    let crypto_instruments = vec![
        ("BTC_PERP", "Cryptocurrency"),     // 8 chars, contains BTC
        ("ETH_SPOT", "Cryptocurrency"),     // 8 chars, contains ETH
        ("CRYPTO_BTC", "Cryptocurrency"),   // 10 chars, contains CRYPTO
        ("CRYPTO_INDEX", "Cryptocurrency"), // 12 chars, contains CRYPTO
        ("ETH_FUTURE", "Cryptocurrency"),   // 10 chars, contains ETH
    ];

    for (instrument, expected) in crypto_instruments {
        let sector = config.classify_sector(instrument, None);
        assert_eq!(sector, expected, "Failed for: {}", instrument);
    }
}

#[test]
fn test_asset_classification_edge_case_empty_instrument() {
    let config = AssetClassificationSchema::new();

    let sector = config.classify_sector("", None);
    assert_eq!(sector, "Other");
}

#[test]
fn test_asset_classification_edge_case_invalid_asset_type() {
    let config = AssetClassificationSchema::new();

    // Invalid asset type should fall back to pattern matching
    let sector = config.classify_sector("EURUSD", Some("INVALID"));
    assert_eq!(sector, "Currencies"); // Matches currency pattern
}

#[test]
fn test_asset_classification_case_sensitivity() {
    let config = AssetClassificationSchema::new();

    // Test case sensitivity with crypto pattern (avoid 6-char currency pattern)
    let sector_upper = config.classify_sector("BTC_PERP", None);
    let sector_lower = config.classify_sector("btc_perp", None);

    assert_eq!(sector_upper, "Cryptocurrency");
    assert_eq!(sector_lower, "Other"); // lowercase doesn't match pattern
}

#[test]
fn test_asset_classification_serialization() {
    let config = AssetClassificationSchema::new();

    let json = serde_json::to_string(&config).unwrap();
    assert!(json.contains("EQUITY"));
    assert!(json.contains("Currencies"));

    let deserialized: AssetClassificationSchema = serde_json::from_str(&json).unwrap();
    assert_eq!(deserialized.asset_type_rules.len(), 5);
    assert_eq!(deserialized.currency_patterns.len(), 5);
}

#[test]
fn test_asset_classification_clone() {
    let config = AssetClassificationSchema::new();
    let cloned = config.clone();

    assert_eq!(cloned.asset_type_rules.len(), config.asset_type_rules.len());
    assert_eq!(cloned.default_sectors.len(), config.default_sectors.len());
    assert_eq!(
        cloned.currency_patterns.len(),
        config.currency_patterns.len()
    );
    assert_eq!(cloned.crypto_patterns.len(), config.crypto_patterns.len());
}

#[test]
fn test_asset_classification_debug_format() {
    let config = AssetClassificationSchema::new();
    let debug_str = format!("{:?}", config);

    assert!(debug_str.contains("asset_type_rules"));
    assert!(debug_str.contains("EQUITY"));
    assert!(debug_str.contains("Currencies"));
}

// ============================================================================
// Integration Tests (3 tests)
// ============================================================================

#[test]
fn test_integration_s3_config_with_asset_classification() {
    let s3_config = S3Config {
        bucket_name: "trading-models".to_string(),
        region: "us-east-1".to_string(),
        ..Default::default()
    };

    let asset_config = AssetClassificationSchema::new();

    assert!(s3_config.validate().is_ok());
    assert_eq!(
        asset_config.classify_sector("AAPL", Some("EQUITY")),
        "Equity"
    );
}

#[test]
fn test_integration_config_schema_versioning() {
    let schema_v1 = ConfigSchema {
        id: Uuid::new_v4(),
        version: "1.0.0".to_string(),
        created_at: Utc::now(),
        updated_at: Utc::now(),
    };

    let schema_v2 = ConfigSchema {
        id: schema_v1.id, // Same config, new version
        version: "2.0.0".to_string(),
        created_at: schema_v1.created_at,
        updated_at: Utc::now(),
    };

    assert_eq!(schema_v1.id, schema_v2.id);
    assert_ne!(schema_v1.version, schema_v2.version);
    assert!(schema_v2.updated_at >= schema_v1.updated_at);
}

#[test]
fn test_integration_full_config_serialization() {
    let s3_config = S3Config::default();
    let asset_config = AssetClassificationSchema::new();
    let schema = ConfigSchema {
        id: Uuid::new_v4(),
        version: "1.0.0".to_string(),
        created_at: Utc::now(),
        updated_at: Utc::now(),
    };

    // Test that all configs can be serialized independently
    let s3_json = serde_json::to_string(&s3_config).unwrap();
    let asset_json = serde_json::to_string(&asset_config).unwrap();
    let schema_json = serde_json::to_string(&schema).unwrap();

    assert!(s3_json.contains("foxhunt-models"));
    assert!(asset_json.contains("EQUITY"));
    assert!(schema_json.contains("1.0.0"));

    // Test deserialization
    let _: S3Config = serde_json::from_str(&s3_json).unwrap();
    let _: AssetClassificationSchema = serde_json::from_str(&asset_json).unwrap();
    let _: ConfigSchema = serde_json::from_str(&schema_json).unwrap();
}
