// Copyright 2025 TiKV Project Authors. Licensed under Apache-2.0.
// Modifications Copyright 2026 PingCAP, Inc.
//! Coprocessor online config manager.

use std::sync::{
    Arc,
    atomic::{AtomicU64, Ordering},
};

use online_config::{ConfigChange, ConfigManager, ConfigValue, Result as CfgResult};
use tikv_util::{config::ReadableSize, memory::MemoryQuota};

pub(super) struct CopConfigManager {
    cop_max_resp_size: Arc<AtomicU64>,
    memory_quota: Arc<MemoryQuota>,
}

impl CopConfigManager {
    pub fn new(cop_max_resp_size: Arc<AtomicU64>, memory_quota: Arc<MemoryQuota>) -> Self {
        Self {
            cop_max_resp_size,
            memory_quota,
        }
    }
}

impl ConfigManager for CopConfigManager {
    fn dispatch(&mut self, mut change: ConfigChange) -> CfgResult<()> {
        if let Some(s) = change.remove("cop_max_resp_size") {
            let new_size: ReadableSize = s.into();
            self.cop_max_resp_size.store(new_size.0, Ordering::Relaxed);
        }
        if let Some(quota) = change.remove("end_point_memory_quota") {
            if quota != ConfigValue::None {
                let cap: ReadableSize = quota.into();
                self.memory_quota.set_capacity(cap.0 as _);
            }
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use std::collections::HashMap;

    use super::*;

    // Regression guard: a single dispatch carrying both online keys must
    // update both backing values. Catches a future change that drops the
    // older `cop_max_resp_size` branch when extending this manager.
    #[test]
    fn test_dispatch_handles_both_keys() {
        let max_resp = Arc::new(AtomicU64::new(0));
        let quota = Arc::new(MemoryQuota::new(0));
        let mut mgr = CopConfigManager::new(max_resp.clone(), quota.clone());

        let mut change: ConfigChange = HashMap::new();
        change.insert(
            "cop_max_resp_size".to_owned(),
            ConfigValue::Size(ReadableSize::mb(64).0),
        );
        change.insert(
            "end_point_memory_quota".to_owned(),
            ConfigValue::Size(ReadableSize::mb(128).0),
        );
        mgr.dispatch(change).unwrap();

        assert_eq!(
            max_resp.load(Ordering::Relaxed),
            ReadableSize::mb(64).0,
            "cop_max_resp_size must still flow through after merge"
        );
        assert_eq!(quota.capacity(), ReadableSize::mb(128).0 as usize);
    }

    // `ConfigValue::None` is the no-op signal in online-config; it must not
    // clobber the quota capacity.
    #[test]
    fn test_dispatch_skips_none_quota() {
        let max_resp = Arc::new(AtomicU64::new(0));
        let quota = Arc::new(MemoryQuota::new(ReadableSize::mb(99).0 as _));
        let mut mgr = CopConfigManager::new(max_resp, quota.clone());

        let mut change: ConfigChange = HashMap::new();
        change.insert("end_point_memory_quota".to_owned(), ConfigValue::None);
        mgr.dispatch(change).unwrap();

        assert_eq!(quota.capacity(), ReadableSize::mb(99).0 as usize);
    }
}
