use std::collections::HashMap;
use std::fmt;
use std::io::Read;
use std::path::Path;

use async_trait::async_trait;
use libloading::{Library, Symbol};
use serde::de::{self, Deserialize, Deserializer, MapAccess, Visitor};
use serde::ser::{Serialize, Serializer};
use serde::{Deserialize as DeriveDeserialize, Serialize as DeriveSerialize};
use tracing::{debug, error, info, warn};

use crate::error::RuntimeError;
use crate::runtime::PenguinRuntimeRef;
use crate::Result;

/// 检测当前平台并返回与插件清单平台命名约定匹配的字符串键。
///
/// 支持的平台键：`windows`、`windows_arm`、`linux`、`linux_arm`、`macos`、
/// `macos_x64`、`ohos`、`ohos_x64`、`ios`、`android`。
#[allow(unexpected_cfgs)]
fn current_platform() -> &'static str {
    if cfg!(all(target_os = "windows", target_arch = "aarch64")) {
        "windows_arm"
    } else if cfg!(target_os = "windows") {
        "windows"
    } else if cfg!(all(target_os = "linux", target_arch = "x86_64", target_env = "ohos")) {
        "ohos_x64"
    } else if cfg!(all(target_os = "linux", target_arch = "aarch64", target_env = "ohos")) {
        "ohos"
    } else if cfg!(all(target_os = "linux", target_arch = "aarch64")) {
        "linux_arm"
    } else if cfg!(target_os = "linux") {
        "linux"
    } else if cfg!(all(target_os = "darwin", target_arch = "x86_64")) {
        "macos_x64"
    } else if cfg!(all(target_os = "darwin", target_arch = "aarch64")) {
        "macos"
    } else if cfg!(target_os = "ios") {
        "ios"
    } else if cfg!(target_os = "android") {
        "android"
    } else {
        "unknown"
    }
}

#[derive(Debug, Clone)]
enum PluginFileMapInner {
    Single(String),
    Multi(HashMap<String, String>),
}

/// 表示插件清单中的 `file` 字段，可以是单个文件名或平台到文件名的映射。
///
/// 当从字符串反序列化时，表示单文件插件（所有平台使用同一文件）。
/// 当从对象反序列化时，每个键是平台标识，值是该平台对应的动态库在压缩包中的路径。
#[derive(Debug, Clone)]
pub struct PluginFileMap {
    inner: PluginFileMapInner,
}

impl PluginFileMap {
    /// 解析当前平台的插件文件名。
    ///
    /// 对于单文件插件，直接返回文件名。
    /// 对于多平台插件，查找当前平台对应的文件名。
    /// 如果当前平台未在映射中找到，返回 `None`。
    pub fn resolve(&self) -> Option<&str> {
        match &self.inner {
            PluginFileMapInner::Single(file) => Some(file.as_str()),
            PluginFileMapInner::Multi(files) => files.get(current_platform()).map(|s| s.as_str()),
        }
    }
}

impl Serialize for PluginFileMap {
    fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
        match &self.inner {
            PluginFileMapInner::Single(file) => serializer.serialize_str(file),
            PluginFileMapInner::Multi(files) => files.serialize(serializer),
        }
    }
}

impl<'de> Deserialize<'de> for PluginFileMap {
    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
        struct PluginFileVisitor;

        impl<'de> Visitor<'de> for PluginFileVisitor {
            type Value = PluginFileMap;

            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                f.write_str("a string or a map of platform to file path")
            }

            fn visit_str<E: de::Error>(self, v: &str) -> std::result::Result<PluginFileMap, E> {
                Ok(PluginFileMap {
                    inner: PluginFileMapInner::Single(v.to_string()),
                })
            }

            fn visit_map<M: MapAccess<'de>>(self, map: M) -> std::result::Result<PluginFileMap, M::Error> {
                let files: HashMap<String, String> =
                    HashMap::deserialize(de::value::MapAccessDeserializer::new(map))
                        .map_err(|e| de::Error::custom(e.to_string()))?;
                Ok(PluginFileMap {
                    inner: PluginFileMapInner::Multi(files),
                })
            }
        }

        deserializer.deserialize_any(PluginFileVisitor)
    }
}

/// 插件的依赖项，指定所需的最小版本号。
#[derive(Debug, Clone, DeriveSerialize, DeriveDeserialize)]
pub struct PluginDependency {
    /// 被依赖插件的 ID。
    pub id: String,
    /// 所需的最小 `version-code`。
    #[serde(rename = "version-code")]
    pub version_code: i64,
}

/// 插件元信息，从 `.pgp` 压缩包中的 `penguin-plugin.json` 解析生成。
#[derive(Debug, Clone, DeriveSerialize, DeriveDeserialize)]
pub struct PluginManifest {
    /// 插件的唯一标识。
    pub id: String,
    /// 插件的人类可读名称。
    pub name: String,
    /// 插件二进制文件描述——单个文件名或平台到文件名的映射。
    pub file: PluginFileMap,
    /// 插件的简要描述。
    pub description: String,
    /// 数字版本号（例如 `10000` 对应 1.0.0）。
    #[serde(rename = "version-code")]
    pub version_code: i64,
    /// 人类可读的版本名称（如 `"1.0.0"`）。
    #[serde(rename = "version-name")]
    pub version_name: String,
    /// 插件所需的最低运行时 API 版本。
    pub api: i64,
    /// 作者列表。
    pub authors: Vec<String>,
    /// 此插件依赖的其他插件。
    #[serde(default)]
    pub dependencies: Vec<PluginDependency>,
}

/// 每个插件必须实现的 trait。
///
/// 插件从 `.pgp` 压缩包中动态加载，初始化时会获得运行时的引用，
/// 以便注册 Provider 或执行其他初始化逻辑。
#[async_trait]
pub trait PenguinPlugin: Send + Sync {
    /// 使用运行时引用初始化插件。
    ///
    /// 实现方应在此方法中注册音源提供者、元数据提供者或本地化翻译。
    async fn init(&self, runtime: &dyn PenguinRuntimeRef);
}

/// 已成功加载的插件，包含其元信息与插件实例。
pub struct LoadedPenguinPlugin {
    /// 从 `penguin-plugin.json` 解析得到的清单。
    pub manifest: PluginManifest,
    /// 插件 trait 对象。
    pub plugin: Box<dyn PenguinPlugin>,
}

/// 管理插件的全生命周期，包括从 `.pgp` 压缩包加载插件以及查询已加载的插件。
pub struct PenguinPluginManager {
    loaded_plugins: HashMap<String, LoadedPenguinPlugin>,
}

impl PenguinPluginManager {
    /// 创建一个新的空插件管理器。
    pub fn new() -> Self {
        Self {
            loaded_plugins: HashMap::new(),
        }
    }

    /// 从 `.pgp` 压缩包加载一个插件。
    ///
    /// 加载流程：
    /// 1. 验证 `.pgp` 扩展名。
    /// 2. 以 zip 格式打开文件。
    /// 3. 读取并解析 `penguin-plugin.json`。
    /// 4. 解析当前平台对应的二进制文件名。
    /// 5. 检查插件 ID 是否已被占用。
    /// 6. 验证 API 版本兼容性。
    /// 7. 检查依赖是否满足。
    /// 8. 验证二进制文件是否存在于压缩包中。
    /// 9. 将二进制文件解压到临时目录。
    /// 10. 通过 `libloading` 加载动态库并调用 `penguin_plugin_create`。
    /// 11. 注册 `resources/lang/*.json` 中的翻译文件。
    pub fn load_plugin(
        &mut self,
        path: &Path,
        runtime: &dyn PenguinRuntimeRef,
    ) -> Result<()> {
        info!(path = %path.display(), "loading plugin");

        let ext = path
            .extension()
            .and_then(|e| e.to_str())
            .unwrap_or("");

        if ext != "pgp" {
            warn!(ext, "invalid plugin file extension");
            return Err(RuntimeError::InvalidPluginExtension(
                path.display().to_string(),
            ));
        }
        debug!("extension check passed");

        let file =
            std::fs::File::open(path).map_err(|e| RuntimeError::PluginFileNotFound(e.to_string()))?;

        let mut archive =
            zip::ZipArchive::new(file).map_err(|e| RuntimeError::InvalidPluginArchive(e.to_string()))?;
        debug!("zip archive opened successfully");

        let manifest_str = {
            let mut found = false;
            let mut content = String::new();
            for i in 0..archive.len() {
                let mut entry = archive.by_index(i)?;
                if entry.name() == "penguin-plugin.json" {
                    entry.read_to_string(&mut content)?;
                    found = true;
                    break;
                }
            }
            if !found {
                error!("penguin-plugin.json not found in archive");
                return Err(RuntimeError::MissingPluginManifest);
            }
            content
        };

        let manifest: PluginManifest = serde_json::from_str(&manifest_str)
            .map_err(|e| RuntimeError::InvalidPluginManifest(e.to_string()))?;
        info!(id = %manifest.id, name = %manifest.name, version = %manifest.version_name, "plugin manifest parsed");

        let plugin_file = manifest.file.resolve().ok_or_else(|| {
            let platform = current_platform();
            error!(platform, "no matching plugin file for current platform");
            RuntimeError::InvalidPluginManifest(format!(
                "no matching plugin file for platform '{}'",
                platform
            ))
        })?;
        debug!(plugin_file, "resolved plugin binary");

        if self.loaded_plugins.contains_key(&manifest.id) {
            warn!(id = %manifest.id, "plugin already loaded");
            return Err(RuntimeError::PluginAlreadyLoaded(manifest.id));
        }

        let current_version = runtime.get_core_version();
        if manifest.api > current_version {
            error!(api = manifest.api, current = current_version, "API version too high");
            return Err(RuntimeError::ApiVersionTooHigh {
                api: manifest.api,
                current: current_version,
            });
        }
        debug!("API version check passed");

        for dep in &manifest.dependencies {
            let satisfied = self.loaded_plugins.get(&dep.id).map_or(false, |loaded| {
                loaded.manifest.version_code >= dep.version_code
            });
            if !satisfied {
                error!(dep = %dep.id, required = dep.version_code, "unsatisfied dependency");
                return Err(RuntimeError::UnsatisfiedDependency {
                    id: dep.id.clone(),
                    version_code: dep.version_code,
                });
            }
        }
        debug!("dependency check passed");

        let plugin_file_exists = (0..archive.len()).any(|i| {
            archive
                .by_index(i)
                .map(|e| e.name() == plugin_file)
                .unwrap_or(false)
        });
        if !plugin_file_exists {
            error!(plugin_file, "binary not found in archive");
            return Err(RuntimeError::InvalidPluginManifest(format!(
                "plugin file '{}' not found in archive",
                plugin_file
            )));
        }
        debug!("plugin binary exists in archive");

        let temp_dir = std::env::temp_dir().join(format!("penguin_plugin_{}", manifest.id));
        std::fs::create_dir_all(&temp_dir)?;
        let plugin_path = temp_dir.join(plugin_file);
        debug!(path = %plugin_path.display(), "extracting plugin binary");

        for i in 0..archive.len() {
            let mut entry = archive.by_index(i)?;
            if entry.name() == plugin_file {
                let mut out = std::fs::File::create(&plugin_path)?;
                std::io::copy(&mut entry, &mut out)?;
                break;
            }
        }

        let plugin: Box<dyn PenguinPlugin> = unsafe {
            debug!("loading dynamic library");
            let library = Library::new(&plugin_path)
                .map_err(|e| RuntimeError::LibraryError(e.to_string()))?;

            let create_fn: Symbol<
                unsafe extern "C" fn() -> *mut std::ffi::c_void,
            > = library
                .get(b"penguin_plugin_create")
                .map_err(|e| RuntimeError::LibraryError(e.to_string()))?;

            let ptr = create_fn();
            if ptr.is_null() {
                return Err(RuntimeError::LibraryError(
                    "penguin_plugin_create returned null".to_string(),
                ));
            }

            let boxed_plugin: Box<Box<dyn PenguinPlugin>> =
                Box::from_raw(ptr as *mut Box<dyn PenguinPlugin>);
            let plugin: Box<dyn PenguinPlugin> = *boxed_plugin;

            std::mem::forget(library);

            plugin
        };
        debug!("dynamic library loaded, plugin instance created");

        let loaded = LoadedPenguinPlugin {
            manifest: manifest.clone(),
            plugin,
        };

        let mut lang_count = 0u32;
        for i in 0..archive.len() {
            let mut entry = archive.by_index(i)?;
            let name = entry.name().to_string();
            if name.starts_with("resources/lang/") && name.ends_with(".json") {
                let lang = name
                    .trim_start_matches("resources/lang/")
                    .trim_end_matches(".json");
                let mut content = String::new();
                entry.read_to_string(&mut content)?;
                runtime.register_translations(lang, &content)?;
                lang_count += 1;
            }
        }
        debug!(lang_count, "language resources registered");

        self.loaded_plugins
            .insert(manifest.id.clone(), loaded);
        info!(id = %manifest.id, "plugin loaded successfully");

        Ok(())
    }

    /// 返回所有已加载插件的列表。
    pub fn list_loaded_plugins(&self) -> Vec<&LoadedPenguinPlugin> {
        self.loaded_plugins.values().collect()
    }
}

impl Default for PenguinPluginManager {
    fn default() -> Self {
        Self::new()
    }
}
