//! VersionManager — 插件版本切换、回退、稳定标记
//!
//! 管理 plugin_store/ 中插件的版本生命周期。

use crate::core::plugin_loader::PluginLoader;
use anyhow::{anyhow, Context, Result};
use std::collections::HashSet;

/// 版本管理器
pub struct VersionManager {
    loader: PluginLoader,
}

/// 版本信息
#[derive(Debug, Clone, serde::Serialize)]
pub struct VersionInfo {
    pub version: String,
    pub is_active: bool,
    pub is_stable: bool,
}

impl VersionManager {
    pub fn new(loader: PluginLoader) -> Self {
        Self { loader }
    }

    pub fn loader(&self) -> &PluginLoader {
        &self.loader
    }

    pub fn loader_mut(&mut self) -> &mut PluginLoader {
        &mut self.loader
    }

    /// 标记当前活跃版本为稳定版本
    pub fn mark_stable(&self, plugin_id: &str, version: &str) -> Result<()> {
        let mut registry = self.loader.load_registry()?;
        let entry = registry
            .plugins
            .get_mut(plugin_id)
            .ok_or_else(|| anyhow!("plugin '{plugin_id}' not in registry"))?;

        entry.last_stable_version = Some(version.to_string());
        self.loader.save_registry(&registry)?;

        println!("[VersionManager] 插件 '{plugin_id}' v{version} 标记为稳定版本");
        Ok(())
    }

    /// 回退到上一个稳定版本，返回回退到的版本号
    pub fn rollback(&self, plugin_id: &str) -> Result<String> {
        let mut registry = self.loader.load_registry()?;
        let entry = registry
            .plugins
            .get_mut(plugin_id)
            .ok_or_else(|| anyhow!("plugin '{plugin_id}' not in registry"))?;

        let stable_version = entry
            .last_stable_version
            .clone()
            .ok_or_else(|| anyhow!("plugin '{plugin_id}' has no stable version to rollback to"))?;

        if stable_version == entry.active_version {
            return Err(anyhow!(
                "plugin '{plugin_id}' is already at stable version {stable_version}"
            ));
        }

        let old_version = entry.active_version.clone();
        entry.active_version = stable_version.clone();
        self.loader.save_registry(&registry)?;

        println!("[VersionManager] 插件 '{plugin_id}' 从 v{old_version} 回退到 v{stable_version}");
        Ok(stable_version)
    }

    /// 切换到指定版本
    pub fn switch_version(&self, plugin_id: &str, version: &str) -> Result<()> {
        // 验证版本目录存在
        let version_dir = self.loader.store_path().join(plugin_id).join(version);
        if !version_dir.exists() {
            return Err(anyhow!(
                "version {version} not found for plugin '{plugin_id}'"
            ));
        }

        let mut registry = self.loader.load_registry()?;
        let entry = registry
            .plugins
            .get_mut(plugin_id)
            .ok_or_else(|| anyhow!("plugin '{plugin_id}' not in registry"))?;

        entry.active_version = version.to_string();
        self.loader.save_registry(&registry)?;

        println!("[VersionManager] 插件 '{plugin_id}' 切换到 v{version}");
        Ok(())
    }

    /// 列出插件的所有版本信息
    pub fn list_versions(&self, plugin_id: &str) -> Result<Vec<VersionInfo>> {
        let versions = self.loader.list_versions(plugin_id)?;
        let registry = self.loader.load_registry()?;
        let entry = registry.plugins.get(plugin_id);

        Ok(versions
            .into_iter()
            .map(|v| {
                let is_active = entry.map_or(false, |e| e.active_version == v);
                let is_stable =
                    entry.map_or(false, |e| e.last_stable_version.as_deref() == Some(&v));
                VersionInfo {
                    version: v,
                    is_active,
                    is_stable,
                }
            })
            .collect())
    }

    /// 垃圾回收：保留最近 N 个版本，删除旧版本
    /// 不删除活跃版本和稳定版本
    pub fn gc(&self, plugin_id: &str, keep: usize) -> Result<Vec<String>> {
        let versions = self.loader.list_versions(plugin_id)?;
        let registry = self.loader.load_registry()?;
        let entry = registry.plugins.get(plugin_id);

        let active = entry.map(|e| e.active_version.as_str());
        let stable = entry.and_then(|e| e.last_stable_version.as_deref());
        let protected_count = [active, stable]
            .into_iter()
            .flatten()
            .collect::<HashSet<_>>()
            .len();

        // 保护活跃版本和稳定版本
        let mut deletable: Vec<&str> = versions
            .iter()
            .map(|s| s.as_str())
            .filter(|v| Some(*v) != active && Some(*v) != stable)
            .collect();

        // 保留最近的 keep 个（版本排在后面的更新）
        let mut removed = Vec::new();
        while deletable.len() + protected_count > keep && !deletable.is_empty() {
            let oldest = deletable.remove(0);
            let version_dir = self.loader.store_path().join(plugin_id).join(oldest);
            if version_dir.exists() {
                std::fs::remove_dir_all(&version_dir)
                    .with_context(|| format!("failed to remove {}", version_dir.display()))?;
                removed.push(oldest.to_string());
                println!("[VersionManager] 已清理 '{plugin_id}' v{oldest}");
            }
        }

        Ok(removed)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::core::plugin_loader::{ErrorPolicy, PluginRegistry, PluginRegistryEntry};
    use std::fs;
    use std::path::Path;

    fn setup(base: &Path) -> VersionManager {
        setup_with_entry(
            base,
            PluginRegistryEntry {
                active_version: "1.1.0".to_string(),
                last_stable_version: Some("1.0.0".to_string()),
                enabled: true,
                error_policy: ErrorPolicy::AutoRollback,
                max_errors: 5,
            },
        )
    }

    fn setup_with_entry(base: &Path, entry: PluginRegistryEntry) -> VersionManager {
        let _ = fs::remove_dir_all(base);
        fs::create_dir_all(base).unwrap();

        let loader = PluginLoader::new(base);

        // 创建两个版本目录
        for v in &["1.0.0", "1.1.0", "2.0.0"] {
            fs::create_dir_all(base.join("test-plugin").join(v)).unwrap();
        }

        // 写入注册表
        let mut registry = PluginRegistry::default();
        registry.plugins.insert("test-plugin".to_string(), entry);
        loader.save_registry(&registry).unwrap();

        VersionManager::new(loader)
    }

    #[test]
    fn rollback_switches_to_stable_version() {
        let tmp = std::env::temp_dir().join("showen_test_rollback");
        let vm = setup(&tmp);

        let rolled = vm.rollback("test-plugin").unwrap();
        assert_eq!(rolled, "1.0.0");

        let reg = vm.loader().load_registry().unwrap();
        assert_eq!(reg.plugins["test-plugin"].active_version, "1.0.0");

        let _ = fs::remove_dir_all(&tmp);
    }

    #[test]
    fn switch_version_updates_active() {
        let tmp = std::env::temp_dir().join("showen_test_switch");
        let vm = setup(&tmp);

        vm.switch_version("test-plugin", "2.0.0").unwrap();

        let reg = vm.loader().load_registry().unwrap();
        assert_eq!(reg.plugins["test-plugin"].active_version, "2.0.0");

        let _ = fs::remove_dir_all(&tmp);
    }

    #[test]
    fn mark_stable_updates_registry() {
        let tmp = std::env::temp_dir().join("showen_test_mark_stable");
        let vm = setup(&tmp);

        vm.mark_stable("test-plugin", "1.1.0").unwrap();

        let reg = vm.loader().load_registry().unwrap();
        assert_eq!(
            reg.plugins["test-plugin"].last_stable_version,
            Some("1.1.0".to_string())
        );

        let _ = fs::remove_dir_all(&tmp);
    }

    #[test]
    fn list_versions_shows_flags() {
        let tmp = std::env::temp_dir().join("showen_test_list_ver");
        let vm = setup(&tmp);

        let versions = vm.list_versions("test-plugin").unwrap();
        assert_eq!(versions.len(), 3);

        let v100 = versions.iter().find(|v| v.version == "1.0.0").unwrap();
        assert!(!v100.is_active);
        assert!(v100.is_stable);

        let v110 = versions.iter().find(|v| v.version == "1.1.0").unwrap();
        assert!(v110.is_active);
        assert!(!v110.is_stable);

        let _ = fs::remove_dir_all(&tmp);
    }

    #[test]
    fn gc_removes_old_versions() {
        let tmp = std::env::temp_dir().join("showen_test_gc");
        let vm = setup(&tmp);

        // keep=2: active(1.1.0) + stable(1.0.0) are protected, 2.0.0 is deletable
        // With keep=2, since we have 2 protected + 1 deletable = 3, and we want to keep at most 2 total...
        // Actually the gc tries to keep `keep` total versions including protected ones
        let removed = vm.gc("test-plugin", 2).unwrap();
        assert_eq!(removed, vec!["2.0.0"]);

        let versions = vm.loader().list_versions("test-plugin").unwrap();
        assert_eq!(versions.len(), 2);

        let _ = fs::remove_dir_all(&tmp);
    }

    #[test]
    fn gc_keeps_overlap_once_when_active_equals_stable() {
        let tmp = std::env::temp_dir().join("showen_test_gc_overlap");
        let vm = setup_with_entry(
            &tmp,
            PluginRegistryEntry {
                active_version: "1.1.0".to_string(),
                last_stable_version: Some("1.1.0".to_string()),
                enabled: true,
                error_policy: ErrorPolicy::AutoRollback,
                max_errors: 5,
            },
        );

        let removed = vm.gc("test-plugin", 2).unwrap();
        assert_eq!(removed, vec!["1.0.0"]);

        let versions = vm.loader().list_versions("test-plugin").unwrap();
        assert_eq!(versions, vec!["1.1.0", "2.0.0"]);

        let _ = fs::remove_dir_all(&tmp);
    }

    #[test]
    fn gc_keeps_active_when_stable_is_none() {
        let tmp = std::env::temp_dir().join("showen_test_gc_no_stable");
        let vm = setup_with_entry(
            &tmp,
            PluginRegistryEntry {
                active_version: "1.1.0".to_string(),
                last_stable_version: None,
                enabled: true,
                error_policy: ErrorPolicy::AutoRollback,
                max_errors: 5,
            },
        );

        let removed = vm.gc("test-plugin", 1).unwrap();
        assert_eq!(removed, vec!["1.0.0", "2.0.0"]);

        let versions = vm.loader().list_versions("test-plugin").unwrap();
        assert_eq!(versions, vec!["1.1.0"]);

        let _ = fs::remove_dir_all(&tmp);
    }

    #[test]
    fn gc_does_not_remove_protected_versions_when_keep_is_smaller() {
        let tmp = std::env::temp_dir().join("showen_test_gc_keep_smaller");
        let vm = setup(&tmp);

        let removed = vm.gc("test-plugin", 1).unwrap();
        assert_eq!(removed, vec!["2.0.0"]);

        let versions = vm.loader().list_versions("test-plugin").unwrap();
        assert_eq!(versions, vec!["1.0.0", "1.1.0"]);

        let _ = fs::remove_dir_all(&tmp);
    }
}
