// Copyright 2026 PingCAP, Inc.

use std::collections::HashMap;

use serde::{Deserialize, Serialize};
use tikv_util::{sys::SysQuota, warn};

/// CPU throttle configuration
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(default)]
#[serde(rename_all = "kebab-case")]
pub struct CpuThrottleConfig {
    /// Whether CPU throttling is enabled
    pub enabled: bool,

    /// Keyspace CPU quotas in format "keyspace_id:millicores,..."
    /// Example: "1:1000,2:2000,3:500" means keyspace 1 gets 1 core, keyspace 2
    /// gets 2 cores, etc.
    pub keyspace_cpu_quotas: String,

    /// Uniform per-tenant CPU cap ratio for Essential.
    /// 0.0 disables tenant cap mode.
    /// > 0.0 auto-creates per-keyspace buckets with this cap against node CPU.
    pub tenant_max_cpu_ratio: f64,

    /// Idle TTL in milliseconds for auto-created tenant cap buckets.
    pub tenant_cap_idle_ttl_ms: u64,

    /// Estimated CPU time per request in microseconds
    /// Used as the initial token allocation for each request
    pub estimated_cpu_per_request_us: u64,

    /// Whether to enable the low-priority DAG global bucket.
    pub low_priority_dag_global_enabled: bool,

    /// The share of the global read CPU budget reserved for low-priority DAG
    /// requests.
    pub low_priority_dag_global_ratio: f64,

    /// Keyspace-specific estimated CPU time per request in microseconds
    /// Used as initial estimates when adaptive adjustment is enabled
    /// Format: "keyspace_id:cpu_us,..."
    /// Example: "2:1500,3:5000"
    pub keyspace_estimated_cpu_per_request_us: String,

    /// Enable adaptive adjustment for keyspace estimated CPU per request
    /// When enabled, estimates are updated from observed CPU usage
    pub enable_adaptive_estimated_cpu_per_request_us: bool,

    /// Stats interval in milliseconds for adaptive estimate updates
    pub stats_interval_ms: u64,

    /// Token refill interval in milliseconds
    pub refill_interval_ms: u64,

    /// Maximum duration of CPU tokens retained by a bucket, in milliseconds.
    /// The 100ms default preserves the pre-decoupling default bucket capacity;
    /// a larger value allows a proportionally larger short-term burst.
    pub burst_window_ms: u64,

    /// Enable dynamic refill rate adjustment based on CPU watermarks
    pub enable_dynamic_adjustment: bool,

    /// High watermark threshold for CPU usage (0.0 - 1.0)
    /// When CPU usage exceeds this threshold, refill rate will be reduced
    pub high_watermark: f64,

    /// Low watermark threshold for CPU usage (0.0 - 1.0)
    /// When CPU usage falls below this threshold, refill rate will be restored
    pub low_watermark: f64,

    /// Enable the legacy throttle path's proportional burst-share guard.
    /// This is separate from keyspace fairness in the read pool. When enabled
    /// and global CPU exceeds `fair_allocation_threshold`, the per-interval
    /// burst budget is allocated proportionally based on keyspace quotas.
    pub enable_fair_allocation: bool,

    /// Legacy burst fair-allocation threshold (0.0 - 1.0).
    /// Fair allocation kicks in when global CPU usage exceeds this threshold.
    pub fair_allocation_threshold: f64,

    /// Enable burst capability
    /// When enabled, keyspaces can exceed their quota if global CPU has spare
    /// capacity
    pub enable_burst: bool,

    /// Burst threshold (0.0 - 1.0)
    /// Burst is allowed when global CPU usage is below this threshold
    pub burst_threshold: f64,

    /// Keyspace-specific burst enable flags in format
    /// "keyspace_id:true/false,..." Example: "1:true,2:false"
    pub keyspace_burst_enabled: String,

    /// Enable runtime CPU token management
    /// When enabled, tokens will be dynamically allocated during request
    /// execution
    pub enable_runtime_token_management: bool,

    /// Runtime check interval in microseconds
    /// How often to check CPU consumption and allocate additional tokens
    pub runtime_check_interval_us: u64,

    /// Additional allocation threshold (0.0 - 1.0)
    /// Allocate more tokens when used CPU exceeds this fraction of allocated
    /// tokens Example: if 0.5, allocate more when using 50% of current
    /// allocation
    pub additional_allocation_threshold: f64,

    /// Additional CPU tokens to allocate per allocation in microseconds
    /// Must satisfy: per_allocation_us >= runtime_check_interval_us * 2
    /// This constraint ensures tokens are allocated faster than they can be
    /// consumed
    pub per_allocation_us: u64,

    /// Whether to enable debug logging for resource control (e.g. CPU throttle
    /// events) When true, verbose logs such as dynamic burst mode switch
    /// are printed
    pub debug: bool,

    /// Whether to enforce global CPU limit for keyspace allocations
    /// When false (default): keyspace quotas are enforced; global bucket is
    /// best-effort tracking When true: allocations must succeed in both
    /// global and keyspace buckets (legacy behavior)
    ///
    /// Recommended: false for hard isolation between keyspaces
    /// Use true only when sum(keyspace_quotas) > global_quota and you want to
    /// enforce global limit
    pub enforce_global_limit: bool,

    /// Enable periodic fetch of keyspace service_limit from PD
    pub enable_pd_service_limit: bool,

    /// PD service_limit fetch interval in milliseconds
    pub pd_service_limit_fetch_interval_ms: u64,

    /// PD service_limit request timeout in milliseconds
    pub pd_service_limit_request_timeout_ms: u64,

    /// TTL for handling keyspace 404 responses in milliseconds
    pub pd_service_limit_ttl_ms: u64,

    /// Consecutive 404 threshold to remove keyspace quota
    pub pd_service_limit_consecutive_404: u64,

    /// Update threshold for service_limit changes (0.0 - 1.0)
    pub pd_service_limit_update_threshold: f64,

    /// Whether PD values override config quotas when enabled
    pub pd_override_config: bool,
}

impl Default for CpuThrottleConfig {
    fn default() -> Self {
        crate::Config::default().to_cpu_throttle_config()
    }
}

impl CpuThrottleConfig {
    /// Convert millicores to microseconds per refill interval
    ///
    /// # Arguments
    /// * `millicores` - CPU quota in millicores
    /// * `refill_interval_ms` - Refill interval in milliseconds
    ///
    /// # Returns
    /// CPU quota in microseconds per refill interval
    ///
    /// # Formula
    /// 1 core = 1000 millicores
    /// 1 core running for `refill_interval_ms` = refill_interval_ms * 1000
    /// microseconds So: quota_us = (millicores / 1000) *
    /// (refill_interval_ms * 1000)
    pub fn millicores_to_us_per_interval(millicores: u64, refill_interval_ms: u64) -> u64 {
        if millicores == 0 {
            return u64::MAX; // No limit
        }
        millicores.saturating_mul(refill_interval_ms)
    }

    /// Parse keyspace CPU quotas from string format
    ///
    /// # Arguments
    /// * `quotas_str` - Quota string in format "keyspace_id:millicores,..."
    /// * `refill_interval_ms` - Refill interval in milliseconds
    ///
    /// # Returns
    /// HashMap of keyspace_id -> quota in microseconds per refill interval
    ///
    /// # Example
    /// ```
    /// use resource_control::CpuThrottleConfig;
    /// let quotas = CpuThrottleConfig::parse_keyspace_quotas("1:1000,2:2000", 100);
    /// assert_eq!(quotas.get(&1), Some(&100000)); // 1 core * 100ms interval
    /// assert_eq!(quotas.get(&2), Some(&200000)); // 2 cores * 100ms interval
    /// ```
    pub fn parse_keyspace_quotas(quotas_str: &str, refill_interval_ms: u64) -> HashMap<u32, u64> {
        let mut quotas = HashMap::new();

        if quotas_str.is_empty() {
            return quotas;
        }

        for entry in quotas_str.split(',') {
            let parts: Vec<&str> = entry.trim().split(':').collect();
            if parts.len() != 2 {
                warn!(
                    "Invalid keyspace quota format: {}. Expected 'keyspace_id:millicores'",
                    entry
                );
                continue;
            }

            match (parts[0].parse::<u32>(), parts[1].parse::<u64>()) {
                (Ok(keyspace_id), Ok(millicores)) => {
                    let quota_us =
                        Self::millicores_to_us_per_interval(millicores, refill_interval_ms);
                    quotas.insert(keyspace_id, quota_us);
                }
                _ => {
                    warn!(
                        "Failed to parse keyspace quota: {}. Expected numeric values",
                        entry
                    );
                }
            }
        }

        quotas
    }

    /// Get keyspace CPU quotas in microseconds per refill interval
    pub fn keyspace_cpu_us(&self) -> HashMap<u32, u64> {
        Self::parse_keyspace_quotas(&self.keyspace_cpu_quotas, self.refill_interval_ms)
    }

    pub fn compute_tenant_cap_quota_us_for(
        tenant_max_cpu_ratio: f64,
        refill_interval_ms: u64,
    ) -> Option<u64> {
        if tenant_max_cpu_ratio <= 0.0 {
            return None;
        }
        let cpu_cores = SysQuota::cpu_cores_quota().max(1.0);
        Some((cpu_cores * tenant_max_cpu_ratio * refill_interval_ms as f64 * 1000.0) as u64)
    }

    pub fn compute_tenant_cap_quota_us(&self) -> Option<u64> {
        Self::compute_tenant_cap_quota_us_for(self.tenant_max_cpu_ratio, self.refill_interval_ms)
    }

    /// Parse keyspace estimated CPU time per request from string format
    ///
    /// # Arguments
    /// * `estimated_str` - Format "keyspace_id:cpu_us,..."
    pub fn parse_keyspace_estimated_cpu_per_request_us(estimated_str: &str) -> HashMap<u32, u64> {
        let mut overrides = HashMap::new();

        if estimated_str.is_empty() {
            return overrides;
        }

        for entry in estimated_str.split(',') {
            let parts: Vec<&str> = entry.trim().split(':').collect();
            if parts.len() != 2 {
                warn!(
                    "Invalid keyspace estimated cpu format: {}. Expected 'keyspace_id:cpu_us'",
                    entry
                );
                continue;
            }

            match (parts[0].parse::<u32>(), parts[1].parse::<u64>()) {
                (Ok(keyspace_id), Ok(cpu_us)) => {
                    if cpu_us == 0 {
                        warn!(
                            "Keyspace estimated cpu cannot be 0; skipping";
                            "keyspace_id" => keyspace_id,
                        );
                        continue;
                    }
                    overrides.insert(keyspace_id, cpu_us);
                }
                _ => {
                    warn!(
                        "Failed to parse keyspace estimated cpu: {}. Expected numeric values",
                        entry
                    );
                }
            }
        }

        overrides
    }

    /// Get keyspace estimated CPU time per request override map
    pub fn keyspace_estimated_cpu_per_request_us_map(&self) -> HashMap<u32, u64> {
        Self::parse_keyspace_estimated_cpu_per_request_us(
            &self.keyspace_estimated_cpu_per_request_us,
        )
    }

    /// Parse keyspace burst enabled flags from string format
    ///
    /// # Arguments
    /// * `burst_str` - Format "keyspace_id:true/false,..."
    pub fn parse_keyspace_burst_enabled(burst_str: &str) -> HashMap<u32, bool> {
        let mut overrides = HashMap::new();

        if burst_str.is_empty() {
            return overrides;
        }

        for entry in burst_str.split(',') {
            let parts: Vec<&str> = entry.trim().split(':').collect();
            if parts.len() != 2 {
                warn!(
                    "Invalid keyspace burst enabled format: {}. Expected 'keyspace_id:true/false'",
                    entry
                );
                continue;
            }

            let enabled = match parts[1].trim().to_ascii_lowercase().as_str() {
                "true" | "1" | "yes" | "on" => Some(true),
                "false" | "0" | "no" | "off" => Some(false),
                _ => None,
            };

            match (parts[0].parse::<u32>(), enabled) {
                (Ok(keyspace_id), Some(enabled)) => {
                    overrides.insert(keyspace_id, enabled);
                }
                _ => {
                    warn!(
                        "Failed to parse keyspace burst enabled entry: {}. Expected 'keyspace_id:true/false'",
                        entry
                    );
                }
            }
        }

        overrides
    }

    /// Validate configuration
    pub fn validate(&self) -> Result<(), String> {
        if self.high_watermark <= self.low_watermark {
            return Err(format!(
                "high_watermark ({}) must be greater than low_watermark ({})",
                self.high_watermark, self.low_watermark
            ));
        }

        if self.high_watermark > 1.0 || self.low_watermark < 0.0 {
            return Err(format!(
                "Watermarks must be in range [0.0, 1.0], got high={}, low={}",
                self.high_watermark, self.low_watermark
            ));
        }

        if self.fair_allocation_threshold > 1.0 || self.fair_allocation_threshold < 0.0 {
            return Err(format!(
                "fair_allocation_threshold must be in range [0.0, 1.0], got {}",
                self.fair_allocation_threshold
            ));
        }

        if self.burst_threshold > 1.0 || self.burst_threshold < 0.0 {
            return Err(format!(
                "burst_threshold must be in range [0.0, 1.0], got {}",
                self.burst_threshold
            ));
        }

        if self.refill_interval_ms == 0 {
            return Err("refill_interval_ms must be greater than 0".to_string());
        }
        if self.burst_window_ms < self.refill_interval_ms {
            return Err("burst_window_ms must not be less than refill_interval_ms".to_string());
        }

        if !(0.0..=1.0).contains(&self.tenant_max_cpu_ratio) {
            return Err(format!(
                "tenant_max_cpu_ratio must be in range [0.0, 1.0], got {}",
                self.tenant_max_cpu_ratio
            ));
        }
        if self.tenant_max_cpu_ratio > 0.0 {
            let quota_us = Self::compute_tenant_cap_quota_us_for(
                self.tenant_max_cpu_ratio,
                self.refill_interval_ms,
            )
            .unwrap_or(0);
            if quota_us == 0 {
                return Err(
                    "tenant_max_cpu_ratio is too small, resulting in a 0us quota".to_string(),
                );
            }
        }

        if self.tenant_cap_idle_ttl_ms == 0 {
            return Err("tenant_cap_idle_ttl_ms must be greater than 0".to_string());
        }

        if self.tenant_max_cpu_ratio > 0.0 {
            if !self.enforce_global_limit {
                return Err(
                    "enforce_global_limit must be true when tenant_max_cpu_ratio is enabled"
                        .to_string(),
                );
            }
            if !self.keyspace_cpu_quotas.is_empty() {
                return Err(
                    "keyspace_cpu_quotas must be empty when tenant_max_cpu_ratio is enabled"
                        .to_string(),
                );
            }
            if self.enable_pd_service_limit {
                return Err(
                    "enable_pd_service_limit must be false when tenant_max_cpu_ratio is enabled"
                        .to_string(),
                );
            }
            if self.enable_fair_allocation {
                return Err(
                    "enable_fair_allocation must be false when tenant_max_cpu_ratio is enabled"
                        .to_string(),
                );
            }
            if self.enable_dynamic_adjustment {
                return Err(
                    "enable_dynamic_adjustment must be false when tenant_max_cpu_ratio is enabled"
                        .to_string(),
                );
            }
            if self.enable_burst {
                return Err(
                    "enable_burst must be false when tenant_max_cpu_ratio is enabled".to_string(),
                );
            }
            if self.low_priority_dag_global_enabled {
                return Err(
                    "low_priority_dag_global_enabled must be false when tenant_max_cpu_ratio is enabled"
                        .to_string(),
                );
            }
        }

        // Validate runtime token management parameters
        if self.enable_runtime_token_management {
            if self.runtime_check_interval_us == 0 {
                return Err("runtime_check_interval_us must be greater than 0".to_string());
            }

            if self.additional_allocation_threshold > 1.0
                || self.additional_allocation_threshold < 0.0
            {
                return Err(format!(
                    "additional_allocation_threshold must be in range [0.0, 1.0], got {}",
                    self.additional_allocation_threshold
                ));
            }

            if self.per_allocation_us == 0 {
                return Err("per_allocation_us must be greater than 0".to_string());
            }

            // Constraint: per_allocation_us >= runtime_check_interval_us * 2
            // This ensures we allocate tokens faster than they can be consumed
            if self.per_allocation_us < self.runtime_check_interval_us * 2 {
                return Err(format!(
                    "per_allocation_us ({}) must be >= runtime_check_interval_us * 2 ({})",
                    self.per_allocation_us,
                    self.runtime_check_interval_us * 2
                ));
            }
        }

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_millicores_to_us_conversion() {
        // 1 core = 1000 millicores
        // 1 core for 100ms interval = 100,000 microseconds
        assert_eq!(
            CpuThrottleConfig::millicores_to_us_per_interval(1000, 100),
            100_000
        );

        // 2 cores for 100ms interval = 200,000 microseconds
        assert_eq!(
            CpuThrottleConfig::millicores_to_us_per_interval(2000, 100),
            200_000
        );

        // 0.5 cores (500 millicores) for 100ms interval = 50,000 microseconds
        assert_eq!(
            CpuThrottleConfig::millicores_to_us_per_interval(500, 100),
            50_000
        );

        // 0 millicores = no limit (u64::MAX)
        assert_eq!(
            CpuThrottleConfig::millicores_to_us_per_interval(0, 100),
            u64::MAX
        );
    }

    #[test]
    fn test_parse_keyspace_quotas() {
        let quotas = CpuThrottleConfig::parse_keyspace_quotas("1:1000,2:2000,3:500", 100);

        assert_eq!(quotas.len(), 3);
        assert_eq!(quotas.get(&1), Some(&100_000)); // 1 core
        assert_eq!(quotas.get(&2), Some(&200_000)); // 2 cores
        assert_eq!(quotas.get(&3), Some(&50_000)); // 0.5 cores
    }

    #[test]
    fn test_parse_keyspace_quotas_empty() {
        let quotas = CpuThrottleConfig::parse_keyspace_quotas("", 100);
        assert_eq!(quotas.len(), 0);
    }

    #[test]
    fn test_parse_keyspace_quotas_invalid() {
        // Invalid format should be skipped
        let quotas = CpuThrottleConfig::parse_keyspace_quotas("1:1000,invalid,2:2000", 100);
        assert_eq!(quotas.len(), 2);
        assert_eq!(quotas.get(&1), Some(&100_000));
        assert_eq!(quotas.get(&2), Some(&200_000));
    }

    #[test]
    fn test_config_keyspace_cpu_us() {
        let mut config = CpuThrottleConfig::default();
        config.keyspace_cpu_quotas = "1:1000,2:2000".to_string();
        config.refill_interval_ms = 100;

        let quotas = config.keyspace_cpu_us();
        assert_eq!(quotas.len(), 2);
        assert_eq!(quotas.get(&1), Some(&100_000));
        assert_eq!(quotas.get(&2), Some(&200_000));
    }

    #[test]
    fn test_config_validation() {
        let mut config = CpuThrottleConfig::default();

        // Valid config should pass
        config.validate().unwrap();

        // Invalid watermarks
        config.high_watermark = 0.5;
        config.low_watermark = 0.8;
        assert!(config.validate().is_err());

        config = CpuThrottleConfig::default();
        config.high_watermark = 1.5;
        assert!(config.validate().is_err());

        config = CpuThrottleConfig::default();
        config.fair_allocation_threshold = 1.5;
        assert!(config.validate().is_err());

        config = CpuThrottleConfig::default();
        config.burst_threshold = -0.1;
        assert!(config.validate().is_err());

        config = CpuThrottleConfig::default();
        config.refill_interval_ms = 0;
        assert!(config.validate().is_err());

        config = CpuThrottleConfig::default();
        config.refill_interval_ms = 101;
        assert!(config.validate().is_err());

        config = CpuThrottleConfig::default();
        config.tenant_cap_idle_ttl_ms = 0;
        assert!(config.validate().is_err());
    }

    #[test]
    fn test_config_default() {
        let config = CpuThrottleConfig::default();

        assert!(!config.enabled);
        assert_eq!(config.estimated_cpu_per_request_us, 1000);
        assert_eq!(config.tenant_max_cpu_ratio, 0.0);
        assert_eq!(config.tenant_cap_idle_ttl_ms, 10 * 60 * 1000);
        assert!(config.keyspace_estimated_cpu_per_request_us.is_empty());
        assert!(config.enable_adaptive_estimated_cpu_per_request_us);
        assert_eq!(config.stats_interval_ms, 1000);
        assert_eq!(config.refill_interval_ms, 10);
        assert_eq!(config.burst_window_ms, 100);
        assert!(!config.enable_dynamic_adjustment);
        assert_eq!(config.high_watermark, 0.8);
        assert_eq!(config.low_watermark, 0.5);
        assert!(!config.enable_fair_allocation);
        assert_eq!(config.fair_allocation_threshold, 0.7);
        assert!(!config.enable_burst);
        assert_eq!(config.burst_threshold, 0.9);
        assert!(config.keyspace_burst_enabled.is_empty());
        assert!(config.enable_runtime_token_management);
        assert_eq!(config.runtime_check_interval_us, 1000);
        assert_eq!(config.additional_allocation_threshold, 0.5);
        assert_eq!(config.per_allocation_us, 2000);
        assert!(!config.enforce_global_limit);
    }

    #[test]
    fn test_runtime_token_management_validation() {
        let mut config = CpuThrottleConfig::default();

        // Default config should be valid
        config.validate().unwrap();

        // Invalid runtime_check_interval_us
        config.runtime_check_interval_us = 0;
        assert!(config.validate().is_err());

        // Invalid additional_allocation_threshold
        config = CpuThrottleConfig::default();
        config.additional_allocation_threshold = 1.5;
        assert!(config.validate().is_err());

        // Invalid per_allocation_us (too small)
        config = CpuThrottleConfig::default();
        config.runtime_check_interval_us = 2000;
        config.per_allocation_us = 2000; // Less than runtime_check_interval_us * 2
        assert!(config.validate().is_err());

        // Valid constraint: per_allocation_us >= runtime_check_interval_us * 2
        config = CpuThrottleConfig::default();
        config.runtime_check_interval_us = 1000;
        config.per_allocation_us = 2000;
        config.validate().unwrap();

        // Disabled runtime token management should skip validation
        config = CpuThrottleConfig::default();
        config.enable_runtime_token_management = false;
        config.per_allocation_us = 100; // Would fail if enabled
        config.validate().unwrap();
    }

    #[test]
    fn test_tenant_max_cpu_ratio_validation() {
        let mut config = CpuThrottleConfig::default();
        config.tenant_max_cpu_ratio = 1.1;
        assert!(config.validate().is_err());

        config = CpuThrottleConfig::default();
        config.tenant_max_cpu_ratio = 0.4;
        config.enforce_global_limit = true;
        config.enable_pd_service_limit = false;
        config.validate().unwrap();

        config.keyspace_cpu_quotas = "1:1000".to_string();
        assert!(config.validate().is_err());

        config = CpuThrottleConfig::default();
        config.tenant_max_cpu_ratio = 0.4;
        config.enforce_global_limit = false;
        config.enable_pd_service_limit = false;
        assert!(config.validate().is_err());

        config.enforce_global_limit = true;
        config.enable_pd_service_limit = true;
        assert!(config.validate().is_err());

        config.enable_pd_service_limit = false;
        config.enable_burst = true;
        assert!(config.validate().is_err());

        config.enable_burst = false;
        config.enable_fair_allocation = true;
        assert!(config.validate().is_err());

        config.enable_fair_allocation = false;
        config.enable_dynamic_adjustment = true;
        assert!(config.validate().is_err());

        config.enable_dynamic_adjustment = false;
        config.low_priority_dag_global_enabled = true;
        assert!(config.validate().is_err());
    }

    #[test]
    fn test_tenant_max_cpu_ratio_rejects_zero_us_quota() {
        let mut config = CpuThrottleConfig::default();
        config.tenant_max_cpu_ratio = f64::MIN_POSITIVE;
        config.enforce_global_limit = true;
        config.enable_pd_service_limit = false;

        let err = config.validate().unwrap_err();
        assert!(err.contains("0us quota"));
    }

    #[test]
    fn test_parse_keyspace_estimated_cpu_per_request_us() {
        let overrides =
            CpuThrottleConfig::parse_keyspace_estimated_cpu_per_request_us("1:1000,2:2000,3:500");

        assert_eq!(overrides.len(), 3);
        assert_eq!(overrides.get(&1), Some(&1000));
        assert_eq!(overrides.get(&2), Some(&2000));
        assert_eq!(overrides.get(&3), Some(&500));
    }

    #[test]
    fn test_parse_keyspace_estimated_cpu_per_request_us_invalid() {
        let overrides = CpuThrottleConfig::parse_keyspace_estimated_cpu_per_request_us(
            "1:1000,invalid,2:0,3:2000",
        );

        assert_eq!(overrides.len(), 2);
        assert_eq!(overrides.get(&1), Some(&1000));
        assert_eq!(overrides.get(&3), Some(&2000));
    }

    #[test]
    fn test_parse_keyspace_burst_enabled() {
        let overrides =
            CpuThrottleConfig::parse_keyspace_burst_enabled("1:true,2:false,3:1,4:0,5:on,6:off");

        assert_eq!(overrides.len(), 6);
        assert_eq!(overrides.get(&1), Some(&true));
        assert_eq!(overrides.get(&2), Some(&false));
        assert_eq!(overrides.get(&3), Some(&true));
        assert_eq!(overrides.get(&4), Some(&false));
        assert_eq!(overrides.get(&5), Some(&true));
        assert_eq!(overrides.get(&6), Some(&false));
    }

    #[test]
    fn test_parse_keyspace_burst_enabled_invalid() {
        let overrides =
            CpuThrottleConfig::parse_keyspace_burst_enabled("1:true,invalid,2:maybe,3:false");

        assert_eq!(overrides.len(), 2);
        assert_eq!(overrides.get(&1), Some(&true));
        assert_eq!(overrides.get(&3), Some(&false));
    }
}
