//! PluginLoader — 扫描 plugin_store/ 目录，发现并加载动态插件
//!
//! 目录结构:
//! ```text
//! plugin_store/
//! ├── registry.json
//! └── custom-sensor/
//!     ├── 1.0.0/
//!     │   ├── manifest.json
//!     │   └── libcustom_sensor.so
//!     └── 1.1.0/
//!         ├── manifest.json
//!         └── libcustom_sensor.so
//! ```

use crate::core::dynamic_plugin::DynamicPlugin;
use anyhow::{anyhow, Context, Result};
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};

/// 插件清单 (manifest.json)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginManifest {
    pub id: String,
    pub version: String,
    pub sdk_version: String,
    #[serde(default)]
    pub dependencies: Vec<String>,
    #[serde(default = "default_error_policy")]
    pub error_policy: ErrorPolicy,
    pub so_filename: String,
    /// 插件声明支持的功能列表
    #[serde(default)]
    pub capabilities: Vec<String>,
    /// 挂载时必须通过测试的功能（capabilities 的子集）
    #[serde(default)]
    pub required_capabilities: Vec<String>,
    /// 自测超时（毫秒），默认 5000
    #[serde(default = "default_test_timeout")]
    pub test_timeout_ms: u64,
    /// 是否在挂载时自动运行自测，默认 true
    #[serde(default = "default_true")]
    pub auto_test: bool,
}

fn default_error_policy() -> ErrorPolicy {
    ErrorPolicy::AutoRollback
}

fn default_test_timeout() -> u64 {
    5000
}

/// 插件错误处理策略
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ErrorPolicy {
    /// 自动回退到上一个稳定版本
    AutoRollback,
    /// 禁用插件并记录日志
    DisableAndLog,
}

/// 全局注册表 (registry.json)
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct PluginRegistry {
    #[serde(default)]
    pub plugins: std::collections::HashMap<String, PluginRegistryEntry>,
}

/// 注册表中每个插件的条目
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginRegistryEntry {
    pub active_version: String,
    #[serde(default)]
    pub last_stable_version: Option<String>,
    #[serde(default = "default_true")]
    pub enabled: bool,
    #[serde(default = "default_error_policy")]
    pub error_policy: ErrorPolicy,
    #[serde(default = "default_max_errors")]
    pub max_errors: u32,
}

fn default_true() -> bool {
    true
}

fn default_max_errors() -> u32 {
    5
}

/// 插件加载器
pub struct PluginLoader {
    store_path: PathBuf,
}

impl PluginLoader {
    pub fn new(store_path: impl Into<PathBuf>) -> Self {
        Self {
            store_path: store_path.into(),
        }
    }

    /// 获取存储路径
    pub fn store_path(&self) -> &Path {
        &self.store_path
    }

    /// 读取全局注册表
    pub fn load_registry(&self) -> Result<PluginRegistry> {
        let registry_path = self.store_path.join("registry.json");
        if !registry_path.exists() {
            return Ok(PluginRegistry::default());
        }

        let content = std::fs::read_to_string(&registry_path)
            .with_context(|| format!("failed to read {}", registry_path.display()))?;
        serde_json::from_str(&content)
            .with_context(|| format!("failed to parse {}", registry_path.display()))
    }

    /// 保存全局注册表
    pub fn save_registry(&self, registry: &PluginRegistry) -> Result<()> {
        let registry_path = self.store_path.join("registry.json");
        let content =
            serde_json::to_string_pretty(registry).context("failed to serialize registry")?;
        std::fs::write(&registry_path, content)
            .with_context(|| format!("failed to write {}", registry_path.display()))
    }

    /// 发现所有可用插件（扫描目录）
    pub fn discover_plugins(&self) -> Result<Vec<PluginManifest>> {
        let mut manifests = Vec::new();

        if !self.store_path.exists() {
            return Ok(manifests);
        }

        for entry in std::fs::read_dir(&self.store_path)
            .with_context(|| format!("failed to read {}", self.store_path.display()))?
        {
            let entry = entry?;
            let path = entry.path();
            if !path.is_dir() {
                continue;
            }

            // Skip non-plugin entries (e.g. registry.json)
            match path.file_name().and_then(|n| n.to_str()) {
                Some(name) if name != "registry.json" => {}
                _ => continue,
            }

            // 扫描版本子目录
            for version_entry in std::fs::read_dir(&path)? {
                let version_entry = version_entry?;
                let version_path = version_entry.path();
                if !version_path.is_dir() {
                    continue;
                }

                let manifest_path = version_path.join("manifest.json");
                if manifest_path.exists() {
                    match self.read_manifest(&manifest_path) {
                        Ok(manifest) => manifests.push(manifest),
                        Err(e) => {
                            eprintln!(
                                "[PluginLoader] 跳过无效清单 {}: {e}",
                                manifest_path.display()
                            );
                        }
                    }
                }
            }
        }

        Ok(manifests)
    }

    /// 读取插件清单
    fn read_manifest(&self, path: &Path) -> Result<PluginManifest> {
        let content = std::fs::read_to_string(path)
            .with_context(|| format!("failed to read {}", path.display()))?;
        serde_json::from_str(&content)
            .with_context(|| format!("failed to parse {}", path.display()))
    }

    /// 加载指定插件
    /// version: None 表示使用注册表中的 active_version
    pub fn load_plugin(
        &self,
        plugin_id: &str,
        version: Option<&str>,
    ) -> Result<(DynamicPlugin, PluginManifest)> {
        let version = match version {
            Some(v) => v.to_string(),
            None => {
                let registry = self.load_registry()?;
                registry
                    .plugins
                    .get(plugin_id)
                    .map(|e| e.active_version.clone())
                    .ok_or_else(|| anyhow!("plugin '{plugin_id}' not found in registry"))?
            }
        };

        let version_dir = self.store_path.join(plugin_id).join(&version);
        let manifest_path = version_dir.join("manifest.json");
        let manifest = self.read_manifest(&manifest_path)?;

        if manifest.id != plugin_id {
            return Err(anyhow!(
                "plugin manifest id mismatch: requested '{plugin_id}', found '{}'",
                manifest.id
            ));
        }

        if manifest.version != version {
            return Err(anyhow!(
                "plugin manifest version mismatch: requested '{version}', found '{}'",
                manifest.version
            ));
        }

        let so_path = version_dir.join(&manifest.so_filename);
        if !so_path.exists() {
            return Err(anyhow!("plugin .so not found: {}", so_path.display()));
        }

        let so_path_str = so_path.to_string_lossy().to_string();
        let mut plugin =
            unsafe { DynamicPlugin::load(&so_path_str, manifest.dependencies.clone())? };
        plugin.set_id(manifest.id.clone());

        Ok((plugin, manifest))
    }

    /// 列出插件的所有已安装版本
    pub fn list_versions(&self, plugin_id: &str) -> Result<Vec<String>> {
        let plugin_dir = self.store_path.join(plugin_id);
        if !plugin_dir.exists() {
            return Ok(vec![]);
        }

        let mut versions = Vec::new();
        for entry in std::fs::read_dir(&plugin_dir)? {
            let entry = entry?;
            if entry.path().is_dir() {
                if let Some(name) = entry.file_name().to_str() {
                    versions.push(name.to_string());
                }
            }
        }

        versions.sort();
        Ok(versions)
    }
}

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

    fn unique_test_dir(name: &str) -> std::path::PathBuf {
        let nanos = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .expect("system time should be after unix epoch")
            .as_nanos();
        std::env::temp_dir().join(format!("showen_plugin_loader_{name}_{nanos}"))
    }

    fn setup_test_store(base: &Path) {
        let plugin_dir = base.join("test-plugin").join("1.0.0");
        fs::create_dir_all(&plugin_dir).unwrap();

        let manifest = PluginManifest {
            id: "test-plugin".to_string(),
            version: "1.0.0".to_string(),
            sdk_version: "0.2.0".to_string(),
            dependencies: vec![],
            error_policy: ErrorPolicy::AutoRollback,
            so_filename: "libtest_plugin.so".to_string(),
            capabilities: vec![],
            required_capabilities: vec![],
            test_timeout_ms: 5000,
            auto_test: true,
        };

        fs::write(
            plugin_dir.join("manifest.json"),
            serde_json::to_string_pretty(&manifest).unwrap(),
        )
        .unwrap();
    }

    #[test]
    fn discover_plugins_finds_manifests() {
        let tmp = std::env::temp_dir().join("showen_test_discover");
        let _ = fs::remove_dir_all(&tmp);
        fs::create_dir_all(&tmp).unwrap();
        setup_test_store(&tmp);

        let loader = PluginLoader::new(&tmp);
        let manifests = loader.discover_plugins().unwrap();

        assert_eq!(manifests.len(), 1);
        assert_eq!(manifests[0].id, "test-plugin");
        assert_eq!(manifests[0].version, "1.0.0");

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

    #[test]
    fn empty_store_returns_no_plugins() {
        let tmp = std::env::temp_dir().join("showen_test_empty");
        let _ = fs::remove_dir_all(&tmp);

        let loader = PluginLoader::new(&tmp);
        let manifests = loader.discover_plugins().unwrap();
        assert!(manifests.is_empty());
    }

    #[test]
    fn registry_round_trip() {
        let tmp = std::env::temp_dir().join("showen_test_registry");
        let _ = fs::remove_dir_all(&tmp);
        fs::create_dir_all(&tmp).unwrap();

        let loader = PluginLoader::new(&tmp);

        let mut registry = PluginRegistry::default();
        registry.plugins.insert(
            "test-plugin".to_string(),
            PluginRegistryEntry {
                active_version: "1.0.0".to_string(),
                last_stable_version: None,
                enabled: true,
                error_policy: ErrorPolicy::AutoRollback,
                max_errors: 5,
            },
        );

        loader.save_registry(&registry).unwrap();
        let loaded = loader.load_registry().unwrap();

        assert_eq!(loaded.plugins.len(), 1);
        let entry = &loaded.plugins["test-plugin"];
        assert_eq!(entry.active_version, "1.0.0");
        assert!(entry.enabled);

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

    #[test]
    fn list_versions_returns_sorted() {
        let tmp = std::env::temp_dir().join("showen_test_versions");
        let _ = fs::remove_dir_all(&tmp);

        let plugin_dir = tmp.join("my-plugin");
        fs::create_dir_all(plugin_dir.join("1.1.0")).unwrap();
        fs::create_dir_all(plugin_dir.join("1.0.0")).unwrap();
        fs::create_dir_all(plugin_dir.join("2.0.0")).unwrap();

        let loader = PluginLoader::new(&tmp);
        let versions = loader.list_versions("my-plugin").unwrap();
        assert_eq!(versions, vec!["1.0.0", "1.1.0", "2.0.0"]);

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

    #[test]
    fn manifest_parses_capabilities() {
        let json = r#"{
            "id": "sensor",
            "version": "1.0.0",
            "sdk_version": "0.2.0",
            "so_filename": "libsensor.so",
            "capabilities": ["temperature", "humidity"],
            "required_capabilities": ["temperature"],
            "test_timeout_ms": 3000,
            "auto_test": true
        }"#;
        let manifest: PluginManifest = serde_json::from_str(json).unwrap();
        assert_eq!(manifest.capabilities, vec!["temperature", "humidity"]);
        assert_eq!(manifest.required_capabilities, vec!["temperature"]);
        assert_eq!(manifest.test_timeout_ms, 3000);
        assert!(manifest.auto_test);
    }

    #[test]
    fn manifest_capabilities_default_empty() {
        let json = r#"{
            "id": "basic",
            "version": "1.0.0",
            "sdk_version": "0.2.0",
            "so_filename": "libbasic.so"
        }"#;
        let manifest: PluginManifest = serde_json::from_str(json).unwrap();
        assert!(manifest.capabilities.is_empty());
        assert!(manifest.required_capabilities.is_empty());
        assert_eq!(manifest.test_timeout_ms, 5000);
        assert!(manifest.auto_test);
    }

    #[test]
    fn manifest_test_timeout_ms_is_configurable_from_manifest() {
        let json = r#"{
            "id": "timed-plugin",
            "version": "1.0.0",
            "sdk_version": "0.2.0",
            "so_filename": "libtimed_plugin.so",
            "test_timeout_ms": 12000
        }"#;
        let manifest: PluginManifest = serde_json::from_str(json).unwrap();

        assert_eq!(manifest.test_timeout_ms, 12000);
    }

    #[test]
    fn load_plugin_rejects_manifest_id_mismatch() {
        let tmp = unique_test_dir("id_mismatch");
        let plugin_dir = tmp.join("expected-plugin").join("1.0.0");
        fs::create_dir_all(&plugin_dir).unwrap();
        fs::write(
            plugin_dir.join("manifest.json"),
            r#"{
                "id": "other-plugin",
                "version": "1.0.0",
                "sdk_version": "0.2.0",
                "so_filename": "libexpected_plugin.so"
            }"#,
        )
        .unwrap();

        let loader = PluginLoader::new(&tmp);
        let error = match loader.load_plugin("expected-plugin", Some("1.0.0")) {
            Ok(_) => panic!("id mismatch should be rejected"),
            Err(error) => error,
        };

        assert!(error.to_string().contains(
            "plugin manifest id mismatch: requested 'expected-plugin', found 'other-plugin'"
        ));

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

    #[test]
    fn load_plugin_rejects_manifest_version_mismatch() {
        let tmp = unique_test_dir("version_mismatch");
        let plugin_dir = tmp.join("expected-plugin").join("1.0.0");
        fs::create_dir_all(&plugin_dir).unwrap();
        fs::write(
            plugin_dir.join("manifest.json"),
            r#"{
                "id": "expected-plugin",
                "version": "2.0.0",
                "sdk_version": "0.2.0",
                "so_filename": "libexpected_plugin.so"
            }"#,
        )
        .unwrap();

        let loader = PluginLoader::new(&tmp);
        let error = match loader.load_plugin("expected-plugin", Some("1.0.0")) {
            Ok(_) => panic!("version mismatch should be rejected"),
            Err(error) => error,
        };

        assert!(error
            .to_string()
            .contains("plugin manifest version mismatch: requested '1.0.0', found '2.0.0'"));

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