use tauri::{AppHandle, Emitter};
use std::error::Error;
use futures::{future, stream::StreamExt};
use tauri::async_runtime::spawn;

use crate::models::plugin::Plugin;
use crate::models::repository::{RepositorySource, RepositoryPlugin, BulkUpdateProgress, SingleUpdateResult};
use super::version_utils::compare_plugin_versions;

/// Check for updates for multiple plugins
pub async fn check_for_plugin_updates(
    app_handle: AppHandle,
    installed_plugins: Vec<Plugin>,
    repositories_to_check: Vec<RepositorySource>
) -> Result<Vec<Plugin>, String> {
    if installed_plugins.is_empty() {
        return Ok(Vec::new());
    }

    // Clone plugins for returning
    let mut updated_plugins = installed_plugins.clone();

    // Initialize progress
    let total = installed_plugins.len();
    println!("Starting bulk update check for {} plugins", total);

    if let Err(e) = app_handle.emit("bulk_update_start", total) {
        return Err(format!("Failed to emit bulk_update_start event: {}", e));
    }

    // Process plugins in batches to avoid overwhelming the system
    let batch_size = 5;
    for (batch_index, chunk) in installed_plugins.chunks(batch_size).enumerate() {
        let mut batch_futures = Vec::new();

        // Create futures for this batch
        for (i, plugin) in chunk.iter().enumerate() {
            let index = batch_index * batch_size + i;
            let app_handle_clone = app_handle.clone();
            let repos_clone = repositories_to_check.clone();
            let plugin_clone = plugin.clone();

            // Report progress
            let progress = BulkUpdateProgress {
                processed: index,
                total,
                current_plugin_name: plugin.name.clone(),
            };

            if let Err(e) = app_handle_clone.emit("update_check_progress", progress) {
                println!("Failed to emit update progress: {}", e);
            }

            // Add to batch
            batch_futures.push(async move {
                // Process plugin
                println!("Processing update for plugin {}/{}: {}", index + 1, total, plugin_clone.name);
                let result = process_plugin_update(plugin_clone.clone(), &repos_clone).await;
                (index, result)
            });
        }

        // Process batch concurrently
        let batch_results = futures::future::join_all(batch_futures).await;

        // Update plugins with results from this batch
        for (index, updated_plugin) in batch_results {
            if let Some(position) = updated_plugins.iter().position(|p| p.file_path == updated_plugin.file_path) {
                updated_plugins[position] = updated_plugin;
                println!("Updated plugin {}: {}", index, updated_plugins[position].name);
                if let Some(latest) = &updated_plugins[position].latest_version {
                    println!("  Current version: {}, Latest version: {}, Has update: {}",
                             updated_plugins[position].version, latest, updated_plugins[position].has_update);
                }
            }
        }

        // Small delay between batches to avoid overwhelming
        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
    }

    // Final progress report
    let final_progress = BulkUpdateProgress {
        processed: total,
        total,
        current_plugin_name: "Completed".to_string(),
    };

    if let Err(e) = app_handle.emit("update_check_progress", final_progress) {
        println!("Failed to emit final update progress: {}", e);
    }

    println!("Bulk update check completed for {} plugins", total);
    Ok(updated_plugins)
}

/// Check for updates for a single plugin
pub async fn check_single_plugin_update(
    app_handle: AppHandle,
    plugin_to_check: Plugin,
    repositories_to_check: Vec<RepositorySource>,
) -> Result<(), String> { // Returns Result<(), String> because result is sent via event
    // Begin check
    if let Err(e) = app_handle.emit("single_update_check_started", plugin_to_check.name.clone()) {
        return Err(format!("Failed to emit single_update_check_started event: {}", e));
    }

    // Process update
    let updated_plugin = process_plugin_update(plugin_to_check.clone(), &repositories_to_check).await;

    // Create result
    let result = SingleUpdateResult {
        original_file_path: plugin_to_check.file_path.clone(),
        plugin: Some(updated_plugin),
        error: None,
    };

    // Emit result
    if let Err(e) = app_handle.emit("single_update_check_completed", result) {
        return Err(format!("Failed to emit single_update_check_completed event: {}", e));
    }

    Ok(())
}

/// Process update check for a single plugin
async fn process_plugin_update(plugin: Plugin, repositories: &[RepositorySource]) -> Plugin {
    let mut updated_plugin = plugin.clone();
    println!("Checking for updates for plugin: {}", plugin.name);

    // If plugin already has repository info, check that specific repo
    if let (Some(repo_source), Some(repo_id)) = (&plugin.repository_source, &plugin.repository_id) {
        println!("Plugin has existing repository info: {:?}, ID: {}", repo_source, repo_id);
        match crate::lib_get_plugin_details_from_repository(repo_id, repo_source.clone(), None).await {
            Ok(repo_plugin) => {
                println!("Successfully got details from repository for {}", plugin.name);
                update_plugin_from_repo(&mut updated_plugin, &repo_plugin);
            },
            Err(e) => {
                println!("Failed to get update details for {}: {}", plugin.name, e);
            }
        }
        return updated_plugin;
    }

    // Otherwise, try smart matching with repositories
    println!("No repository info for {}. Trying smart matching...", plugin.name);
    let matches = match crate::search_with_variations(&plugin.name, repositories).await {
        Ok(matches) => {
            println!("Found {} potential matches for {}", matches.len(), plugin.name);
            matches
        },
        Err(e) => {
            println!("Error searching for plugin {}: {}", plugin.name, e);
            return updated_plugin;
        }
    };

    // Find best match
    for repo_plugin in matches {
        if is_likely_match(&plugin, &repo_plugin) {
            println!("Found likely match for {} in {:?}: {} ({})",
                     plugin.name, repo_plugin.repository, repo_plugin.name, repo_plugin.version);
            update_plugin_from_repo(&mut updated_plugin, &repo_plugin);
            break;
        }
    }

    println!("Update check complete for plugin: {}", plugin.name);
    updated_plugin
}

/// Update plugin with repository information
fn update_plugin_from_repo(plugin: &mut Plugin, repo_plugin: &RepositoryPlugin) {
    // Set repository information
    plugin.repository_source = Some(repo_plugin.repository.clone());
    plugin.repository_id = Some(repo_plugin.id.clone());
    plugin.repository_url = Some(repo_plugin.page_url.clone());

    // For Modrinth, check if the version looks like an ID instead of a version number
    let latest_version = if repo_plugin.repository == RepositorySource::Modrinth &&
                          repo_plugin.version.len() >= 8 &&
                          !repo_plugin.version.contains('.') {
        // This looks like an ID rather than a version, use "Latest" as a fallback
        "Latest".to_string()
    } else {
        repo_plugin.version.clone()
    };

    // Set latest version if newer
    if latest_version != plugin.version {
        let has_update = compare_plugin_versions(&plugin.version, &latest_version);
        plugin.latest_version = Some(latest_version);
        plugin.has_update = has_update;
        plugin.changelog = repo_plugin.changelog.clone();
    } else {
        plugin.has_update = false;
    }
}

/// Determine if a repository plugin is likely a match for the installed plugin
fn is_likely_match(installed: &Plugin, repo: &RepositoryPlugin) -> bool {
    // Require multiple criteria to match to avoid false positives
    let mut match_points = 0;

    // Name similarity check
    let name_similarity = calculate_name_similarity(&installed.name, &repo.name);

    // Strong name match (over 0.9 similarity)
    if name_similarity > 0.9 {
        match_points += 2;
    }
    // Moderate name match
    else if name_similarity > 0.7 {
        match_points += 1;
    }

    // Author check - if any author matches
    if !installed.authors.is_empty() && !repo.authors.is_empty() {
        for installed_author in &installed.authors {
            for repo_author in &repo.authors {
                if installed_author.to_lowercase() == repo_author.to_lowercase() {
                    match_points += 2;
                    break;
                }
            }
        }
    }

    // Website match
    if let Some(website) = &installed.website {
        if website.contains(&repo.page_url) || repo.page_url.contains(website) {
            match_points += 2;
        }
    }

    // Require at least 2 match points to consider it a likely match
    // This helps avoid incorrect matches
    match_points >= 2
}

/// Calculate similarity between plugin names (simple implementation)
fn calculate_name_similarity(name1: &str, name2: &str) -> f32 {
    let name1_lower = name1.to_lowercase();
    let name2_lower = name2.to_lowercase();

    // Direct match
    if name1_lower == name2_lower {
        return 1.0;
    }

    // Contains check
    if name1_lower.contains(&name2_lower) || name2_lower.contains(&name1_lower) {
        return 0.9;
    }

    // Simple word matching
    let words1: Vec<&str> = name1_lower.split_whitespace().collect();
    let words2: Vec<&str> = name2_lower.split_whitespace().collect();

    let mut matched_words = 0;
    let total_words = words1.len().max(words2.len());

    for word1 in &words1 {
        for word2 in &words2 {
            if word1 == word2 && word1.len() > 2 {  // Ignore short words
                matched_words += 1;
                break;
            }
        }
    }

    if total_words > 0 {
        matched_words as f32 / total_words as f32
    } else {
        0.0
    }
}