use anyhow::{Context, Result};
use config::Config;
use directories::ProjectDirs;
use reqwest::Client;
use serde::Deserialize;
use std::collections::HashMap;
use std::{fs, path::Path};
use tokio::fs as async_fs;

use crate::config;

#[derive(Deserialize)]
struct Content {
    name: String,
    #[serde(rename = "type")]
    kind: String,
    download_url: Option<String>,
}

fn plugin_library_arch() -> &'static str {
    match std::env::consts::ARCH {
        "aarch64" => "aarch64",
        "x86_64" => "x86_64",
        "arm" => "arm",
        _ => "x86_64",
    }
}

pub async fn load_plugins(deb: bool) -> Result<()> {
    let arch = plugin_library_arch();
    let url = format!("https://api.github.com/repos/rajeev-k47/rootftp/contents/plugin_library/{}", arch);
    let client = Client::new();

    let resp = client
        .get(url)
        .header("User-Agent", "plugin-fetcher")
        .send()
        .await
        .with_context(|| format!("Failed to fetch plugin list"))?;

    let entries: Vec<Content> = resp.json().await.with_context(|| "Failed to parse")?;

    if deb {
        println!("Loading plugins :");
    }
    let plugins: HashMap<String, String> = entries
        .into_iter()
        .filter(|e| e.kind == "file" && e.name.ends_with(".so"))
        .map(|e| {
            let name = e.name.to_string();
            let url = e.download_url.unwrap_or_default();
            if deb {
                println!("{}", name);
            }
            return (name, url);
        })
        .collect();

    if let Some(proj_dirs) = ProjectDirs::from("net", "runner", "rootftp") {
        let config_dir = proj_dirs.config_dir();
        fs::create_dir_all(config_dir).unwrap();
        let config_path = config_dir.join("plugins.json");
        fs::write(config_path, serde_json::to_string_pretty(&plugins)?)?;
    }

    Ok(())
}

pub async fn install_plugin(_name: &str) -> Result<()> {
    if let Some(proj_dirs) = ProjectDirs::from("net", "runner", "rootftp") {
        let config_dir = proj_dirs.config_dir();
        let config_path = config_dir.join("plugins.json");
        let plugin_dir = Config::load().root_dir.join("plugins");

        match read_plugins(&config_path.to_string_lossy()) {
            Ok(plugins) => {
                let mut found = false;
                for (name, url) in plugins {
                    if name == _name {
                        download_plugin(_name, &url, &plugin_dir).await?;
                        found = true;
                    }
                }
                if !found {
                    println!("\nPlugin does not exist.\n");
                    list_plugins().await;
                }
            }
            Err(err) => {
                eprintln!("Failed to install plugin: {}", err);
            }
        }
    }
    Ok(())
}
pub async fn list_plugins() {
    if let Some(proj_dirs) = ProjectDirs::from("net", "runner", "rootftp") {
        let config_dir = proj_dirs.config_dir();
        let config_path = config_dir.join("plugins.json");

        match read_plugins(&config_path.to_string_lossy()) {
            Ok(plugins) => {
                println!("Current available plugins :");
                for (name, _) in plugins {
                    println!("{}", name);
                }
                println!("\nUse <rootftp fetch> to sync plugins with plugin library");
            }
            Err(err) => {
                eprintln!("Error : {}", err);
            }
        }
    }
}

fn read_plugins(path: &str) -> Result<HashMap<String, String>> {
    let content = fs::read_to_string(path)?;
    let plugins: HashMap<String, String> = serde_json::from_str(&content)?;
    Ok(plugins)
}

pub async fn download_plugin(name: &str, url: &str, plugin_dir: &Path) -> Result<()> {
    let dest_path = plugin_dir.join(name);

    let client = Client::new();
    let response = client
        .get(url)
        .header("User-Agent", "plugin-downloader")
        .send()
        .await
        .context("Exception download")?
        .bytes()
        .await
        .context("Exception download")?;

    async_fs::write(&dest_path, &response)
        .await
        .with_context(|| "Failed to save plugin to")?;

    println!("Plugin installed {}", name);
    Ok(())
}
