use std::fs;
use std::path::Path;
use yaml_rust::{YamlLoader, Yaml};
use zip::ZipArchive;

use crate::models::plugin::PluginMeta;
use super::file_utils::{calculate_file_hash, read_yaml_from_archive};

/// Extract metadata from a plugin JAR file
pub fn extract_plugin_metadata(jar_path: &Path) -> Result<PluginMeta, String> {
    // Get the file size
    let metadata = match fs::metadata(jar_path) {
        Ok(meta) => meta,
        Err(e) => return Err(format!("Failed to get file metadata: {}", e)),
    };
    let file_size = metadata.len();

    // Open the JAR file
    let file = match fs::File::open(jar_path) {
        Ok(file) => file,
        Err(e) => return Err(format!("Failed to open JAR file: {}", e)),
    };

    // Create a ZIP archive reader
    let mut archive = match ZipArchive::new(file) {
        Ok(archive) => archive,
        Err(e) => return Err(format!("Failed to read JAR as ZIP archive: {}", e)),
    };

    // Try to read plugin.yml
    let yaml_content = match read_yaml_from_archive(&mut archive, "plugin.yml") {
        Ok(content) => content,
        Err(_) => {
            // If plugin.yml is not found, try bungee.yml for BungeeCord plugins
            match read_yaml_from_archive(&mut archive, "bungee.yml") {
                Ok(content) => content,
                Err(_) => {
                    // If neither is found, use fallback metadata
                    return fallback_plugin_meta(jar_path, file_size);
                }
            }
        }
    };

    // Parse the YAML content
    let docs = match YamlLoader::load_from_str(&yaml_content) {
        Ok(docs) => docs,
        Err(e) => return Err(format!("Failed to parse plugin.yml: {}", e)),
    };

    // If there's no document in the YAML, use fallback
    if docs.is_empty() {
        return fallback_plugin_meta(jar_path, file_size);
    }

    let doc = &docs[0];

    // Extract plugin information
    let name = yaml_str_with_fallback(doc, "name", jar_path);
    let version = yaml_str_with_fallback(doc, "version", jar_path);
    let description = yaml_str_opt(doc, "description");
    let website = yaml_str_opt(doc, "website");
    let api_version = yaml_str_opt(doc, "api-version");
    let main_class = yaml_str_opt(doc, "main");

    // Extract author/authors
    let authors = match &doc["author"] {
        Yaml::String(author) => vec![author.clone()],
        _ => match &doc["authors"] {
            Yaml::Array(authors) => authors
                .iter()
                .filter_map(|a| match a {
                    Yaml::String(s) => Some(s.clone()),
                    _ => None,
                })
                .collect(),
            Yaml::String(author) => vec![author.clone()],
            _ => Vec::new(),
        },
    };

    // Extract dependencies
    let depend = yaml_str_array(doc, "depend");
    let soft_depend = yaml_str_array(doc, "softdepend");
    let load_before = yaml_str_array(doc, "loadbefore");

    // Extract commands and permissions
    let commands = match &doc["commands"] {
        Yaml::Hash(_) => {
            Some(serde_json::Value::String("Commands data present".to_string()))
        },
        _ => None
    };

    let permissions = match &doc["permissions"] {
        Yaml::Hash(_) => {
            Some(serde_json::Value::String("Permissions data present".to_string()))
        },
        _ => None
    };

    // Calculate the file hash
    let file_hash = calculate_file_hash(jar_path.to_str().unwrap_or("unknown.jar")).unwrap_or_else(|_| "unknown".to_string());

    Ok(PluginMeta {
        name,
        version,
        description,
        authors,
        api_version,
        main_class,
        depend,
        soft_depend,
        load_before,
        commands,
        permissions,
        file_path: jar_path.to_string_lossy().to_string(),
        file_size,
        file_hash,
        website,
    })
}

/// Create plugin metadata with fallback values
fn fallback_plugin_meta(jar_path: &Path, file_size: u64) -> Result<PluginMeta, String> {
    let filename = jar_path.file_name()
        .and_then(|n| n.to_str())
        .unwrap_or("unknown.jar");

    // Extract name and version from filename (e.g., "WorldEdit-7.2.8.jar" → name: "WorldEdit", version: "7.2.8")
    let mut parts: Vec<&str> = filename.trim_end_matches(".jar").split('-').collect();
    let version = if parts.len() > 1 {
        parts.pop().unwrap_or("1.0.0").to_string()
    } else {
        "1.0.0".to_string()
    };

    let name = parts.join("-");

    // Calculate hash
    let file_hash = calculate_file_hash(jar_path.to_str().unwrap_or("unknown.jar")).unwrap_or_else(|_| "unknown".to_string());

    Ok(PluginMeta {
        name,
        version,
        description: None,
        authors: Vec::new(),
        website: None,
        api_version: None,
        main_class: None,
        depend: None,
        soft_depend: None,
        load_before: None,
        commands: None,
        permissions: None,
        file_path: jar_path.to_string_lossy().to_string(),
        file_size,
        file_hash,
    })
}

/// Helper function to extract a string with fallback
fn yaml_str_with_fallback(yaml: &Yaml, key: &str, jar_path: &Path) -> String {
    match yaml[key] {
        Yaml::String(ref s) => s.clone(),
        _ => {
            // Fallback to the JAR filename without extension
            let filename = jar_path
                .file_name()
                .and_then(|n| n.to_str())
                .unwrap_or("unknown")
                .trim_end_matches(".jar");

            if key == "name" {
                let name_parts: Vec<&str> = filename.split('-').collect();
                name_parts[0].to_string()
            } else if key == "version" {
                let version_parts: Vec<&str> = filename.split('-').collect();
                if version_parts.len() > 1 {
                    version_parts[1].to_string()
                } else {
                    "1.0.0".to_string()
                }
            } else {
                "".to_string()
            }
        }
    }
}

/// Helper function to extract an optional string
fn yaml_str_opt(yaml: &Yaml, key: &str) -> Option<String> {
    match &yaml[key] {
        Yaml::String(s) => Some(s.clone()),
        _ => None,
    }
}

/// Helper function to extract an array of strings
fn yaml_str_array(yaml: &Yaml, key: &str) -> Option<Vec<String>> {
    match &yaml[key] {
        Yaml::Array(arr) => {
            let string_arr: Vec<String> = arr
                .iter()
                .filter_map(|item| match item {
                    Yaml::String(s) => Some(s.clone()),
                    _ => None,
                })
                .collect();

            if string_arr.is_empty() {
                None
            } else {
                Some(string_arr)
            }
        },
        _ => None,
    }
}