// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/

// Standard library imports
use std::error::Error;
use std::fs::{self, File};
use std::io::{Read, Seek, Write};
use std::path::{Path, PathBuf};
use std::env;
use std::sync::Arc;
use std::time::Duration;

// Serde for serialization/deserialization
use serde::{Serialize, Deserialize};

// Tauri related imports
use tauri::{command, Emitter, AppHandle, Manager, State, Window};

// Internal modules
pub mod models;
pub mod services;
pub mod commands;
pub mod crawlers;
pub mod platform_matcher;

// Import our models
pub use models::server::{ServerType, ServerInfo, ScanResult, ScanProgress};
pub use models::plugin::{Plugin, PluginMeta};
pub use models::repository::{RepositorySource, RepositoryPlugin, PotentialPluginMatch};

// Import our services
pub use services::http::HttpClient;
pub use services::plugin_scanner::{scan_server_directory, perform_scan, extract_plugin_metadata, calculate_file_hash, is_file_locked};
pub use services::update_manager::{check_for_plugin_updates, check_single_plugin_update, backup_plugin, replace_plugin, normalize_version, compare_plugin_versions};

// Import our commands
pub use commands::plugin_commands::*;
pub use commands::scan_commands::*;

// Import our crawlers
pub use crawlers::HangarCrawler;
pub use crawlers::SpigotMCCrawler;
pub use crawlers::ModrinthCrawler;
pub use crawlers::GitHubCrawler;
pub use crawlers::Repository;

// Import platform matchers
pub use platform_matcher::{get_compatible_modrinth_loaders, is_version_compatible_with_server};

use futures::future::{BoxFuture, FutureExt}; // Import necessary future types

/// Search for plugins in repositories
pub async fn lib_search_plugins_in_repositories(
    query: &str,
    repositories: Vec<RepositorySource>
) -> Result<Vec<RepositoryPlugin>, String> {
    // Check for empty query
    if query.is_empty() {
        return Err("Search query cannot be empty".to_string());
    }

    // Check for empty repository list
    if repositories.is_empty() {
        return Err("No repositories specified for search".to_string());
    }

    // Create a list to store tasks (boxed futures)
    let mut tasks: Vec<BoxFuture<Result<(String, Vec<RepositoryPlugin>), (String, String)>>> = Vec::new();

    // Add tasks for each repository
    for repo in repositories {
        match repo {
            RepositorySource::HangarMC => {
                let crawler = crawlers::HangarCrawler::new();
                let query_owned = query.to_string();
                let task = async move {
                 match crawler.search(&query_owned).await {
                    Ok(repo_results) => Ok((crawler.get_repository_name(), repo_results)),
                        Err(e) => Err((crawler.get_repository_name(), e.to_string()))
                    }
                }.boxed(); // Box the future
                tasks.push(task);
            },
            RepositorySource::SpigotMC => {
                let crawler = crawlers::SpigotMCCrawler::new();
                let query_owned = query.to_string();
                let task = async move {
                    match crawler.search(&query_owned).await {
                        Ok(repo_results) => Ok((crawler.get_repository_name(), repo_results)),
                        Err(e) => Err((crawler.get_repository_name(), e.to_string()))
                    }
                }.boxed(); // Box the future
                tasks.push(task);
            },
            RepositorySource::Modrinth => {
                let crawler = crawlers::ModrinthCrawler::new();
                let query_owned = query.to_string();
                let task = async move {
                    match crawler.search(&query_owned).await {
                        Ok(repo_results) => Ok((crawler.get_repository_name(), repo_results)),
                        Err(e) => Err((crawler.get_repository_name(), e.to_string()))
                    }
                }.boxed(); // Box the future
                tasks.push(task);
            },
            RepositorySource::GitHub => {
                // Placeholder - will cause errors until GitHubCrawler implements Repository
                eprintln!("GitHub search not fully implemented yet.");
            },
            _ => {
                eprintln!("Repository {:?} not supported for search", repo);
            }
        }
    }

    // Execute all tasks concurrently
    let results = futures::future::join_all(tasks).await;

    // Collect successful results
    let mut plugins = Vec::new();
    let mut errors = Vec::new();

    for result in results {
         match result {
            Ok((_, repo_plugins)) => {
                plugins.extend(repo_plugins);
            },
            Err((repo, error)) => {
                errors.push(format!("[{}] {}", repo, error));
            }
        }
    }

    // Check if we found any results
    if plugins.is_empty() {
        if !errors.is_empty() {
            return Err(errors.join("; "));
                } else {
            return Ok(Vec::new());
        }
    }

    Ok(plugins)
}

/// Generate search variations for a plugin name
fn generate_search_variations(plugin_name: &str) -> Vec<String> {
    let mut variations = Vec::new();

    // Add original name
    variations.push(plugin_name.to_string());

    // Convert to lowercase
    let name_lower = plugin_name.to_lowercase();
    if name_lower != plugin_name {
        variations.push(name_lower.clone());
    }

    // Add variations with common prefixes/suffixes removed
    let prefixes = ["plugin", "mc", "minecraft"];
    let suffixes = ["plugin", "spigot", "bukkit", "paper", "mc"];

    for prefix in prefixes.iter() {
        let prefix_str = format!("{} ", prefix);
        if name_lower.starts_with(&prefix_str) {
            variations.push(name_lower[prefix_str.len()..].to_string());
        }
    }

    for suffix in suffixes.iter() {
        let suffix_str = format!(" {}", suffix);
        if name_lower.ends_with(&suffix_str) {
            variations.push(name_lower[0..name_lower.len() - suffix_str.len()].to_string());
        }
    }

    // Remove duplicates
    variations.sort();
    variations.dedup();

    variations
}

/// Search for plugin variations
pub async fn search_with_variations(plugin_name: &str, repositories: &[RepositorySource]) -> Result<Vec<RepositoryPlugin>, String> {
    let variations = generate_search_variations(plugin_name);
    let mut all_results = Vec::new();

    for variation in variations {
        match lib_search_plugins_in_repositories(&variation, repositories.to_vec()).await {
            Ok(results) => {
                    all_results.extend(results);
            },
            Err(e) => {
                println!("Error searching for variation '{}': {}", variation, e);
                // Continue with other variations even if this one fails
            }
        }
    }

    // Remove duplicates by plugin ID and repository
    all_results.sort_by(|a, b| {
        let a_key = format!("{:?}:{}", a.repository, a.id);
        let b_key = format!("{:?}:{}", b.repository, b.id);
        a_key.cmp(&b_key)
    });

    all_results.dedup_by(|a, b| {
        a.id == b.id && a.repository == b.repository
    });

    Ok(all_results)
}

/// Get plugin details from a repository
pub async fn lib_get_plugin_details_from_repository(
    plugin_id: &str,
    repository: RepositorySource,
    server_type: Option<&ServerType>
) -> Result<RepositoryPlugin, String> {
    match repository {
        RepositorySource::HangarMC => {
            let crawler = crawlers::HangarCrawler::new();
            crawler.get_plugin_details(plugin_id).await
                .map_err(|e| format!("Failed to get plugin details from HangarMC: {}", e))
        },
        RepositorySource::SpigotMC => {
            let crawler = crawlers::SpigotMCCrawler::new();
            crawler.get_plugin_details(plugin_id).await
                .map_err(|e| format!("Failed to get plugin details from SpigotMC: {}", e))
        },
        RepositorySource::Modrinth => {
            let crawler = crawlers::ModrinthCrawler::new();

            // Use server type aware version if provided
            if let Some(server_type) = server_type {
                crawler.get_plugin_details_with_server_type(plugin_id, Some(server_type)).await
                    .map_err(|e| format!("Failed to get plugin details from Modrinth: {}", e))
    } else {
                crawler.get_plugin_details(plugin_id).await
                    .map_err(|e| format!("Failed to get plugin details from Modrinth: {}", e))
            }
        },
        RepositorySource::GitHub => {
            let crawler = crawlers::GitHubCrawler::new();
            crawler.get_plugin_details(plugin_id).await
                .map_err(|e| format!("Failed to get plugin details from GitHub: {}", e))
        },
        _ => Err(format!("Repository source {:?} not supported for plugin details", repository))
    }
}

/// Download a plugin from a repository
pub async fn lib_download_plugin_from_repository(
    plugin_id: &str,
    version: &str,
    repository: RepositorySource,
    destination: &str,
    server_type: Option<&ServerType>
) -> Result<String, String> {
    match repository {
        RepositorySource::HangarMC => {
            let crawler = crawlers::HangarCrawler::new();
            crawler.download_plugin(plugin_id, version, Path::new(destination)).await
                .map_err(|e| format!("Failed to download plugin from HangarMC: {}", e))
        },
        RepositorySource::SpigotMC => {
            let crawler = crawlers::SpigotMCCrawler::new();
            crawler.download_plugin(plugin_id, version, Path::new(destination)).await
                .map_err(|e| format!("Failed to download plugin from SpigotMC: {}", e))
        },
        RepositorySource::Modrinth => {
            let crawler = crawlers::ModrinthCrawler::new();

            // Use server type aware version if provided
            if let Some(server_type) = server_type {
                crawler.download_plugin_with_server_type(plugin_id, version, Path::new(destination), Some(server_type)).await
                    .map_err(|e| format!("Failed to download plugin from Modrinth: {}", e))
            } else {
                crawler.download_plugin(plugin_id, version, Path::new(destination)).await
                    .map_err(|e| format!("Failed to download plugin from Modrinth: {}", e))
            }
        },
        RepositorySource::GitHub => {
            let crawler = crawlers::GitHubCrawler::new();
            crawler.download_plugin(plugin_id, version, Path::new(destination)).await
                .map_err(|e| format!("Failed to download plugin from GitHub: {}", e))
        },
        _ => Err(format!("Repository source {:?} not supported for downloads", repository))
    }
}

/// Configure and run the Tauri application
pub fn run() {
    // Build the Tauri application
    tauri::Builder::default()
        .plugin(tauri_plugin_dialog::init())
        .invoke_handler(tauri::generate_handler![
            // Plugin discovery commands
            scan_server_dir,
            scan_server_dir_sync,

            // Plugin repository commands
            search_plugins,
            get_plugin_details,

            // Update commands
            update_plugin,
            check_plugin_updates,
            check_single_plugin_update_command,
            backup_plugin_command,

            // Plugin management commands
            download_plugin,
            set_plugin_repository,
            get_plugin_versions,
            load_plugin_data,
            save_plugin_data,

            // Utility commands
            get_potential_plugin_matches,
            compare_versions,
            is_plugin_compatible,
            greet
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
