use std::fs::{self, File};
use std::io::{Read, Write};
use std::path::Path;

fn main() {
    // Get the path to the plugins directory
    let plugins_dir = Path::new("..").join("plugins");

    println!("cargo:warning=Looking for plugins in: {:?}", plugins_dir);

    // Ensure the plugins directory exists
    if !plugins_dir.exists() {
        println!(
            "cargo:warning=Plugins directory not found at {:?}",
            plugins_dir
        );
        return;
    }

    // Find all valid plugin directories
    let plugin_paths = discover_plugins(&plugins_dir);
    println!("cargo:warning=Found {} plugins", plugin_paths.len());

    // Update Cargo.toml with plugin dependencies
    if let Err(e) = update_cargo_toml(&plugin_paths) {
        println!("cargo:warning=Failed to update Cargo.toml: {}", e);
        std::process::exit(1);
    }

    // Generate the plugin macro and imports files
    if let Err(e) = generate_plugin_files(&plugin_paths) {
        println!("cargo:warning=Failed to generate plugin files: {}", e);
        std::process::exit(1);
    }

    // Tell Cargo to rerun this script if the plugins directory or Cargo.toml
    // changes
    println!("cargo:rerun-if-changed=../plugins");
    println!("cargo:rerun-if-changed=Cargo.toml");
}

fn discover_plugins(plugins_dir: &Path) -> Vec<(String, String, String)> {
    let mut valid_plugins = Vec::new();

    if let Ok(entries) = fs::read_dir(plugins_dir) {
        for entry in entries.flatten() {
            let path = entry.path();

            // Check if this is a directory
            if !path.is_dir() {
                continue;
            }

            let plugin_name = path
                .file_name()
                .and_then(|n| n.to_str())
                .unwrap_or("")
                .to_string();

            // Skip if empty plugin name
            if plugin_name.is_empty() {
                continue;
            }

            // Check for required files/directories
            let cargo_toml = path.join("Cargo.toml");
            let src_dir = path.join("src");
            let lib_rs = path.join("src").join("lib.rs");

            if cargo_toml.exists() && src_dir.exists() && lib_rs.exists() {
                // Read the Cargo.toml to get the package name and version
                if let Ok(mut file) = File::open(&cargo_toml) {
                    let mut contents = String::new();
                    if file.read_to_string(&mut contents).is_ok() {
                        // Simple parsing for package name and version
                        let mut name = None;
                        let mut version = None;

                        for line in contents.lines() {
                            let line = line.trim();
                            if line.starts_with("name") {
                                name = line
                                    .split('=')
                                    .nth(1)
                                    .map(|s| s.trim().trim_matches('"').to_string());
                            } else if line.starts_with("version") {
                                version = line
                                    .split('=')
                                    .nth(1)
                                    .map(|s| s.trim().trim_matches('"').to_string());
                            }
                        }

                        if let (Some(name), Some(version)) = (name, version) {
                            println!(
                                "cargo:warning=Found plugin: {} v{} in {}",
                                name, version, plugin_name
                            );
                            valid_plugins.push((name, version, plugin_name));
                        }
                    }
                }
            }
        }
    }

    valid_plugins
}

const AUTO_GENERATED_START: &str =
    "###### BEGIN AUTO-GENERATED PLUGIN DEPENDENCIES - DO NOT EDIT THIS SECTION ######";
const AUTO_GENERATED_END: &str = "###### END AUTO-GENERATED PLUGIN DEPENDENCIES ######";

fn update_cargo_toml(plugin_paths: &[(String, String, String)]) -> std::io::Result<()> {
    let cargo_path = "Cargo.toml";
    let mut contents = String::new();
    File::open(cargo_path)?.read_to_string(&mut contents)?;

    // Normalize line endings to \n for consistent processing
    contents = contents.replace("\r\n", "\n");

    // Find the boundaries of the auto-generated section
    let start_idx = contents.find(AUTO_GENERATED_START);
    let end_idx = contents.find(AUTO_GENERATED_END);

    let base_contents = match (start_idx, end_idx) {
        (Some(start), Some(end)) => {
            // If an existing section is found, take everything before it
            contents[..start].trim_end().to_string()
        }
        _ => {
            // If no section exists, use all current contents
            contents.trim_end().to_string()
        }
    };

    // Generate the new dependencies section
    let mut new_section = String::new();
    new_section.push('\n'); // Add a newline before the section
    new_section.push_str(AUTO_GENERATED_START);
    new_section.push('\n'); // Add newline after start marker

    // Sort plugins by name for consistent output
    let mut sorted_plugins = plugin_paths.to_vec();
    sorted_plugins.sort_by(|a, b| a.0.cmp(&b.0));

    for (name, version, plugin_dir) in sorted_plugins {
        new_section.push_str(&format!(
            "{} = {{ path = \"../plugins/{}\", version = \"{}\" }}\n",
            name, plugin_dir, version
        ));
    }

    new_section.push_str(AUTO_GENERATED_END);

    // Combine the base contents with the new section
    let mut final_contents = base_contents;
    final_contents.push_str(&new_section);

    // Ensure file ends with a single newline
    if !final_contents.ends_with('\n') {
        final_contents.push('\n');
    }

    // Write the updated Cargo.toml
    fs::write(cargo_path, final_contents)?;

    Ok(())
}

fn generate_plugin_files(plugin_paths: &[(String, String, String)]) -> std::io::Result<()> {
    // Create the output directory if it doesn't exist
    let out_dir = Path::new("src");
    fs::create_dir_all(out_dir)?;

    // Then generate the imports file that uses the macro
    generate_imports_file(plugin_paths, out_dir)?;

    Ok(())
}

fn generate_imports_file(
    plugin_paths: &[(String, String, String)],
    out_dir: &Path,
) -> std::io::Result<()> {
    let mut file = fs::File::create(out_dir.join("plugin_imports.rs"))?;

    // Write the header
    writeln!(file, "// This file is automatically generated by build.rs")?;
    writeln!(file, "// Do not edit this file manually!\n")?;
    writeln!(
        file,
        "use horizon_plugin_api::{{Pluginstate, LoadedPlugin, Plugin}};"
    )?;
    writeln!(file, "use std::collections::HashMap;\n")?;
    for (i, (name, _, _)) in plugin_paths.iter().enumerate() {
        write!(file, "pub use {};\n", name)?;
        write!(file, "pub use {}::*;\n", name)?;
        write!(file, "pub use {}::Plugin as {}_plugin;\n", name, name)?;
    }
    writeln!(file, "\n");

    // Use the macro with discovered plugins
    writeln!(file, "// Invoke the macro with all discovered plugins")?;
    writeln!(
        file,
        "pub fn load_plugins() -> HashMap<String, (Pluginstate, Plugin)> {{"
    )?;
    write!(file, "    let plugins = crate::load_plugins!(")?;

    // Add each plugin to the macro invocation
    for (i, (name, _, _)) in plugin_paths.iter().enumerate() {
        if i > 0 {
            write!(file, ",")?;
        }
        write!(file, "\n        {}", name)?;
    }

    writeln!(file, "\n    );")?;
    writeln!(file, "    plugins")?;
    writeln!(file, "}}")?;

    Ok(())
}