use std::error::Error;
use std::path::Path;
use serde::{Serialize, Deserialize};
use crate::{HttpClient, ServerType};
use crate::models::repository::{RepositoryPlugin, RepositorySource};
use crate::platform_matcher::is_version_compatible_with_server;
use urlencoding;
use async_trait::async_trait;
use std::sync::Arc;
use crate::crawlers::Repository;

// Modrinth API response structures (Based on https://docs.modrinth.com/api-spec/)

#[derive(Debug, Serialize, Deserialize)]
struct ModrinthSearchResponse {
    hits: Vec<ModrinthSearchHit>,
    // Omitting pagination fields (offset, limit, total_hits) for simplicity
}

#[derive(Debug, Serialize, Deserialize)]
struct ModrinthSearchHit {
    project_id: String,
    slug: String,         // Use this or project_id for details requests
    project_type: String, // e.g., "mod", "plugin"
    author: String,       // Sometimes different from the project owner
    title: String,
    description: String,
    categories: Vec<String>,
    versions: Vec<String>, // List of supported Minecraft versions
    downloads: u64,
    icon_url: Option<String>,
    latest_version: Option<String>, // Version number of the latest version
    date_modified: String,
    // Omitting client_side, server_side, display_categories, gallery, featured_gallery, license, follows
}

#[derive(Debug, Serialize, Deserialize)]
struct ModrinthProject {
    id: String,
    slug: String,
    project_type: String,
    team: String, // Team ID, need another call potentially for author names?
    title: String,
    description: String,
    body: String, // Full description
    categories: Vec<String>,
    game_versions: Vec<String>,
    downloads: u64,
    icon_url: Option<String>,
    published: String,
    updated: String,
    // Omitting donation_urls, license, client_side, server_side, gallery, status, moderator_message, versions
    // We might need the 'versions' array (list of version IDs) for getting specific version details
}

// Structure for the response from /project/{id}/version
#[derive(Debug, Serialize, Deserialize)]
struct ModrinthVersion {
    id: String,
    project_id: String,
    author_id: String, // User ID, need another call for name?
    name: String,      // Version title
    version_number: String,
    changelog: Option<String>,
    date_published: String,
    downloads: u64,
    version_type: String, // e.g., "release", "beta", "alpha"
    files: Vec<ModrinthVersionFile>,
    game_versions: Vec<String>,
    loaders: Vec<String>,
    // Omitting dependencies, featured, status, requested_status
}

#[derive(Debug, Serialize, Deserialize)]
struct ModrinthVersionFile {
    hashes: ModrinthHashes,
    url: String,
    filename: String,
    primary: bool,
    size: u64,
    // Omitting file_type (useful if filtering non-JARs)
}

#[derive(Debug, Serialize, Deserialize)]
struct ModrinthHashes {
    sha1: String,
    sha512: String,
}

// Modrinth crawler implementation
pub struct ModrinthCrawler {
    client: Arc<HttpClient>,
    api_base_url: String,
}

impl ModrinthCrawler {
    pub fn new() -> Self {
        ModrinthCrawler {
            client: Arc::new(HttpClient::new()),
            api_base_url: "https://api.modrinth.com/v2".to_string(),
        }
    }

    // Helper to get versions, now async
    async fn get_project_versions_internal(&self, plugin_id: &str) -> Result<Vec<ModrinthVersion>, Box<dyn Error + Send + Sync>> {
        let url = format!("{}/project/{}/version", self.api_base_url, plugin_id);
        let response_body = self.client.get(&url).await?;
        let versions: Vec<ModrinthVersion> = serde_json::from_str(&response_body)?;
        Ok(versions)
    }

    // Helper to get compatible versions for a server type
    async fn get_compatible_versions(&self, plugin_id: &str, server_type: Option<&ServerType>) -> Result<Vec<ModrinthVersion>, Box<dyn Error + Send + Sync>> {
        let all_versions = self.get_project_versions_internal(plugin_id).await?;

        // If no server type provided, return all versions
        if server_type.is_none() {
            return Ok(all_versions);
        }

        let server_type = server_type.unwrap();
        let compatible_versions: Vec<ModrinthVersion> = all_versions.into_iter()
            .filter(|version| {
                // Check if this version is compatible with our server
                is_version_compatible_with_server(&version.loaders, server_type)
            })
            .collect();

        // If no compatible versions found, log a warning
        if compatible_versions.is_empty() {
            println!("[ModrinthCrawler::get_compatible_versions] Warning: No versions compatible with server type {:?} for plugin {}", server_type, plugin_id);
        }

        Ok(compatible_versions)
    }

    // Helper to get user details from ID, now async
    async fn get_user_details(&self, user_id: &str) -> Result<ModrinthUser, Box<dyn Error + Send + Sync>> {
        let url = format!("{}/user/{}", self.api_base_url, user_id);
        let response_body = self.client.get(&url).await?;
        let user: ModrinthUser = serde_json::from_str(&response_body)?;
        Ok(user)
    }
}

#[derive(Debug, Serialize, Deserialize)]
struct ModrinthUser {
    username: String,
    // other fields like id, name, avatar_url etc. are available if needed
}

impl ModrinthCrawler {
    pub async fn get_plugin_details_with_server_type(&self, plugin_id: &str, server_type: Option<&ServerType>) -> Result<RepositoryPlugin, String> {
        let project_url = format!("{}/project/{}", self.api_base_url, plugin_id);
        let project_response_body = match self.client.get(&project_url).await {
            Ok(body) => body,
            Err(e) => return Err(format!("Modrinth project request failed: {}", e)),
        };
        let project: ModrinthProject = match serde_json::from_str(&project_response_body) {
            Ok(p) => p,
            Err(e) => return Err(format!("Failed to parse Modrinth project: {}", e)),
        };

        let page_url = format!("https://modrinth.com/plugin/{}", project.slug);

        // Fetch compatible versions
        let versions = match self.get_compatible_versions(&project.id, server_type).await {
            Ok(v) => v,
            Err(e) => {
                println!("Failed to fetch Modrinth versions for {}: {}", plugin_id, e);
                Vec::new() // Continue with empty versions if fetch fails
            }
        };

        let latest_version_opt = versions.first();

        // Explicitly use version_number instead of id
        let latest_version_number = latest_version_opt.map_or("Unknown".to_string(), |v| {
            // Always use the version_number field which is the actual semantic version
            // not the internal Modrinth ID
            v.version_number.clone()
        });

        let changelog = latest_version_opt.and_then(|v| v.changelog.clone());
        let author_id = latest_version_opt.map(|v| v.author_id.clone());

        // Fetch author name if ID is available
        let author_name = if let Some(id) = author_id {
            match self.get_user_details(&id).await {
                Ok(user) => user.username,
                Err(e) => {
                    println!("Failed to fetch Modrinth author details for ID {}: {}", id, e);
                    "Unknown Author".to_string()
                }
            }
        } else {
             project.team.clone() // Fallback to team ID if no version author found
        };

        let primary_file = latest_version_opt.and_then(|v| v.files.iter().find(|f| f.primary));

        Ok(RepositoryPlugin {
            id: project.id.clone(),
            name: project.title,
            version: latest_version_number,
            description: Some(project.description),
            authors: vec![author_name],
            download_url: primary_file.map_or(String::new(), |f| f.url.clone()),
            repository: RepositorySource::Modrinth,
            page_url,
            download_count: Some(project.downloads),
            last_updated: Some(project.updated),
            icon_url: project.icon_url,
            minecraft_versions: latest_version_opt.map_or(project.game_versions.clone(), |v| v.game_versions.clone()),
            categories: project.categories,
            rating: None, // Modrinth API doesn't provide rating directly
            file_size: primary_file.map(|f| f.size),
            file_hash: primary_file.map(|f| f.hashes.sha512.clone()), // Use SHA512
            changelog,
        })
    }

    pub async fn download_plugin_with_server_type(&self, plugin_id: &str, version_number_str: &str, destination: &Path, server_type: Option<&ServerType>) -> Result<String, String> {
        let versions = match self.get_compatible_versions(plugin_id, server_type).await {
            Ok(v) => v,
            Err(e) => return Err(format!("Failed to get Modrinth versions for download: {}", e)),
        };

        let target_version = versions.iter().find(|v| v.version_number == version_number_str);

        let version_to_download = match target_version {
            Some(v) => v,
            None => return Err(format!("Version '{}' not found or not compatible for plugin {}", version_number_str, plugin_id)),
        };

        let primary_file = match version_to_download.files.iter().find(|f| f.primary) {
            Some(f) => f,
            None => return Err(format!("No primary file found for version '{}' of plugin {}", version_number_str, plugin_id)),
        };

        match self.client.download(&primary_file.url, destination).await {
            Ok(_) => Ok(destination.to_string_lossy().to_string()),
            Err(e) => Err(format!("Failed to download from Modrinth: {}", e)),
        }
    }
}

#[async_trait]
impl Repository for ModrinthCrawler {
    fn get_repository_name(&self) -> String {
        "Modrinth".to_string()
    }

    async fn search(&self, query: &str) -> Result<Vec<RepositoryPlugin>, String> {
        let encoded_query = urlencoding::encode(query);
        // Corrected format string with proper quoting for facets
        let url = format!(
            "{}/search?query={}&facets=[[\"project_type:plugin\"]]&limit=20",
            self.api_base_url,
            encoded_query
        );

        let response_body = match self.client.get(&url).await {
            Ok(body) => body,
            Err(e) => return Err(format!("Modrinth search request failed: {}", e)),
        };

        let search_response: ModrinthSearchResponse = match serde_json::from_str(&response_body) {
            Ok(sr) => sr,
            Err(e) => return Err(format!("Failed to parse Modrinth search results: {}", e)),
        };

        let mut results = Vec::new();
        for hit in search_response.hits {
            // For search results, we don't have version_number directly,
            // so we need to fetch the latest version for each project
            let version_info = if let Some(latest_version) = &hit.latest_version {
                // If latest_version looks like an ID (no dots and long), fetch the real version number
                if latest_version.len() >= 8 && !latest_version.contains('.') {
                    match self.get_project_versions_internal(&hit.project_id).await {
                        Ok(versions) if !versions.is_empty() => {
                            versions[0].version_number.clone()
                        },
                        _ => latest_version.clone()
                    }
                } else {
                    latest_version.clone()
                }
            } else {
                "Unknown".to_string()
            };

            // Create a simplified repository plugin from the search hit
            let repo_plugin = RepositoryPlugin {
                id: hit.project_id.clone(),
                name: hit.title,
                version: version_info,
                description: Some(hit.description),
                authors: vec![hit.author],
                download_url: String::new(), // Will be filled in when getting specific version
                repository: RepositorySource::Modrinth,
                page_url: format!("https://modrinth.com/plugin/{}", hit.slug),
                download_count: Some(hit.downloads),
                last_updated: Some(hit.date_modified),
                icon_url: hit.icon_url,
                minecraft_versions: hit.versions,
                categories: hit.categories,
                rating: None,
                file_size: None,
                file_hash: None,
                changelog: None,
            };
            results.push(repo_plugin);
        }

        Ok(results)
    }

    async fn get_plugin_details(&self, plugin_id: &str) -> Result<RepositoryPlugin, String> {
        // Just delegate to our server-type aware version with None
        self.get_plugin_details_with_server_type(plugin_id, None).await
    }

    async fn download_plugin(&self, plugin_id: &str, version_number_str: &str, destination: &Path) -> Result<String, String> {
        // Just delegate to our server-type aware version with None
        self.download_plugin_with_server_type(plugin_id, version_number_str, destination, None).await
    }
}