//! Configuration for profiling parameters

use std::sync::OnceLock;
use std::time::Duration;

/// Benchmark parameter limits and defaults
pub mod limits {
    /// CPU benchmark limits
    pub mod cpu {
        /// Minimum number of threads
        pub const MIN_THREADS: u32 = 1;
        /// Maximum number of threads
        pub const MAX_THREADS: u32 = 256;
        /// Default number of threads
        pub const DEFAULT_THREADS: u32 = 4;

        /// Minimum duration in seconds
        pub const MIN_DURATION_SECS: u64 = 1;
        /// Maximum duration in seconds (1 hour)
        pub const MAX_DURATION_SECS: u64 = 3600;
        /// Default duration in seconds
        pub const DEFAULT_DURATION_SECS: u64 = 30;
    }

    /// Memory benchmark limits
    pub mod memory {
        /// Minimum buffer size in MB
        pub const MIN_SIZE_MB: i64 = 1;
        /// Maximum buffer size in MB (8GB)
        pub const MAX_SIZE_MB: i64 = 8192;
        /// Default buffer size in MB
        pub const DEFAULT_SIZE_MB: i64 = 64;

        /// Minimum iterations
        pub const MIN_ITERATIONS: i64 = 1;
        /// Maximum iterations
        pub const MAX_ITERATIONS: i64 = 100;
        /// Default iterations
        pub const DEFAULT_ITERATIONS: i64 = 5;
    }

    /// Disk benchmark limits
    pub mod disk {
        /// Minimum test size in MB
        pub const MIN_SIZE_MB: i64 = 1;
        /// Maximum test size in MB (10GB)
        pub const MAX_SIZE_MB: i64 = 10240;
        /// Default test size in MB
        pub const DEFAULT_SIZE_MB: i64 = 100;
    }

    /// Network benchmark limits
    pub mod network {
        /// Minimum duration in seconds
        pub const MIN_DURATION_SECS: i64 = 1;
        /// Maximum duration in seconds
        pub const MAX_DURATION_SECS: i64 = 300;
        /// Default duration in seconds
        pub const DEFAULT_DURATION_SECS: i64 = 10;

        /// Minimum ping count
        pub const MIN_PING_COUNT: i64 = 1;
        /// Maximum ping count
        pub const MAX_PING_COUNT: i64 = 100;
        /// Default ping count
        pub const DEFAULT_PING_COUNT: i64 = 5;
    }
}

/// Default configuration values for profiling
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ProfilingConfig {
    // Network configuration
    pub default_iperf_duration: Duration,
    pub default_iperf_port: u16,
    pub default_parallel_streams: u32,
    pub network_timeout: Duration,

    // Disk configuration
    pub default_disk_block_size: u32,
    pub default_disk_test_size: u64,
    pub disk_warmup_iterations: u32,

    // Memory configuration
    pub default_memory_buffer_size: u64,
    pub memory_stress_allocation_limit: u64,

    // CPU configuration
    pub cpu_benchmark_iterations: u64,
    pub prime_sieve_limit: usize,
    pub matrix_size: usize,

    // Cache configuration
    pub l1_cache_size: usize,
    pub l2_cache_size: usize,
    pub l3_cache_size: usize,

    // Monitoring configuration
    pub default_sample_interval: Duration,
    pub concurrent_cpu_threshold: f64,
    pub max_network_bandwidth_gbps: f64,
    pub max_disk_throughput_mbps: f64,
    pub max_memory_bandwidth_gbps: f64,
    pub max_cpu_score: f64,
    pub max_disk_iops: f64,

    // Validation thresholds
    pub max_disk_latency_us: f64,
    pub max_memory_latency_ns: f64,
    pub min_bandwidth_mbps: f64,
}

impl Default for ProfilingConfig {
    fn default() -> Self {
        Self {
            // Network configuration
            default_iperf_duration: Duration::from_secs(10),
            default_iperf_port: 5201,
            default_parallel_streams: 1,
            network_timeout: Duration::from_secs(30),

            // Disk configuration
            default_disk_block_size: 4096,
            default_disk_test_size: 100 * 1024 * 1024, // 100MB
            disk_warmup_iterations: 3,

            // Memory configuration
            default_memory_buffer_size: 100 * 1024 * 1024, // 100MB
            memory_stress_allocation_limit: 1024 * 1024 * 1024, // 1GB

            // CPU configuration
            cpu_benchmark_iterations: 1_000_000,
            prime_sieve_limit: 100_000,
            matrix_size: 100,

            // Cache configuration (will be detected at runtime)
            l1_cache_size: 32 * 1024,       // 32KB default
            l2_cache_size: 256 * 1024,      // 256KB default
            l3_cache_size: 8 * 1024 * 1024, // 8MB default

            // Monitoring configuration
            default_sample_interval: Duration::from_millis(100),
            concurrent_cpu_threshold: 80.0,
            max_network_bandwidth_gbps: 100.0, // 100 Gbps
            max_disk_throughput_mbps: 50000.0, // 50 GB/s (accommodates modern NVMe SSDs)
            max_memory_bandwidth_gbps: 1000.0, // 1 TB/s
            max_cpu_score: 1_000_000_000.0,    // 1 billion
            max_disk_iops: 1_000_000.0,        // 1M IOPS

            // Validation thresholds
            max_disk_latency_us: 10000.0,  // 10ms
            max_memory_latency_ns: 1000.0, // 1μs
            min_bandwidth_mbps: 1.0,       // 1 Mbps
        }
    }
}

impl ProfilingConfig {
    /// Create configuration with auto-detected cache sizes
    pub fn with_detected_cache_sizes(mut self) -> Self {
        if let Ok((l1, l2, l3)) = detect_cache_sizes() {
            self.l1_cache_size = l1;
            self.l2_cache_size = l2;
            self.l3_cache_size = l3;
        }
        self
    }

    /// Validate configuration values
    pub fn validate(&self) -> Result<(), super::error::ProfilingError> {
        if self.default_disk_block_size == 0 {
            return Err(super::error::ProfilingError::InvalidConfig(
                "Disk block size cannot be zero".to_string(),
            ));
        }

        if self.default_disk_test_size == 0 {
            return Err(super::error::ProfilingError::InvalidConfig(
                "Disk test size cannot be zero".to_string(),
            ));
        }

        if self.memory_stress_allocation_limit == 0 {
            return Err(super::error::ProfilingError::InvalidConfig(
                "Memory allocation limit cannot be zero".to_string(),
            ));
        }

        if self.concurrent_cpu_threshold < 0.0 || self.concurrent_cpu_threshold > 100.0 {
            return Err(super::error::ProfilingError::InvalidConfig(
                "CPU threshold must be between 0 and 100".to_string(),
            ));
        }

        Ok(())
    }
}

/// Detect cache sizes from system
fn detect_cache_sizes() -> Result<(usize, usize, usize), super::error::ProfilingError> {
    use std::fs;

    let mut l1_size = 32 * 1024; // Default fallback
    let mut l2_size = 256 * 1024; // Default fallback
    let mut l3_size = 8 * 1024 * 1024; // Default fallback

    // Try to read from /sys/devices/system/cpu/cpu0/cache
    let cache_base = "/sys/devices/system/cpu/cpu0/cache";

    if let Ok(entries) = fs::read_dir(cache_base) {
        for entry in entries.flatten() {
            let path = entry.path();
            if let Some(level_str) = path.file_name().and_then(|n| n.to_str()) {
                if level_str.starts_with("index") {
                    if let Ok(level_content) = fs::read_to_string(path.join("level")) {
                        let level = level_content.trim().parse::<u32>().unwrap_or(0);
                        if let Ok(size_content) = fs::read_to_string(path.join("size")) {
                            let size_str = size_content.trim().trim_end_matches('K');
                            if let Ok(size_kb) = size_str.parse::<usize>() {
                                let size_bytes = size_kb * 1024;
                                match level {
                                    1 => l1_size = size_bytes,
                                    2 => l2_size = size_bytes,
                                    3 => l3_size = size_bytes,
                                    _ => {}
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    Ok((l1_size, l2_size, l3_size))
}

/// Global configuration instance
static GLOBAL_CONFIG: OnceLock<ProfilingConfig> = OnceLock::new();

/// Initialize global configuration
pub fn init_global_config(config: ProfilingConfig) -> Result<(), super::error::ProfilingError> {
    GLOBAL_CONFIG.set(config).map_err(|_| {
        super::error::ProfilingError::InvalidConfig("Configuration already initialized".to_string())
    })
}

/// Get global configuration (panics if not initialized)
pub fn global_config() -> &'static ProfilingConfig {
    GLOBAL_CONFIG.get().unwrap_or_else(|| {
        // Initialize with default if not set
        GLOBAL_CONFIG.set(ProfilingConfig::default()).ok();
        GLOBAL_CONFIG.get().unwrap()
    })
}

/// Get global configuration or default
pub fn global_config_or_default() -> &'static ProfilingConfig {
    GLOBAL_CONFIG.get_or_init(|| ProfilingConfig::default())
}
