CarbonJS 0.1.4

A KubeJS script manager
use crate::utils::convert_int_to_version;
use colored::Colorize;
use git2::{Branch, BranchType, FetchOptions, RemoteCallbacks, Repository};
use reqwest::{Client, StatusCode};
use serde_json::Value;
use std::error::Error;
use std::fs::{self, DirEntry};
use std::path::{Path, PathBuf};

use crate::files::parser::{Config, KjspkgConfig, Package};

fn find_carbon_file(entry: DirEntry, name: &str, is_carbon: bool) -> Option<PathBuf> {
    let path = entry.path();
    let file = if is_carbon {
        "carbon.config.json"
    } else {
        ".kjspkg"
    };
    if path.is_file() && path.file_name().unwrap().to_str().unwrap().eq(file) {
        Some(path.to_owned())
    } else {
        None
    }
}

fn find_scripts_folder(entry: DirEntry) -> Option<PathBuf> {
    let path = entry.path();
    if path.is_dir() {
        match path.file_name() {
            Some(name)
                if name == "startup_scripts"
                    || name == "client_scripts"
                    || name == "server_scripts"
                    || name == "assets" =>
            {
                Some(path.to_owned())
            }
            _ => None,
        }
    } else {
        None
    }
}

pub fn get_files_from_repo(
    repo_url: &str,
    branch_name: &str,
    kubejs_dir: &Path,
    name: &str,
    is_carbon: bool,
    repo_name: &str,
) -> Result<
    (
        PathBuf,
        Vec<PathBuf>,
        Vec<PathBuf>,
        Vec<PathBuf>,
        Vec<PathBuf>,
        Vec<PathBuf>,
    ),
    Box<dyn std::error::Error>,
> {
    let temp_dir = tempfile::tempdir().map_err(|e| format!("Failed to create temp dir: {}", e))?;

    let mut builder = git2::build::RepoBuilder::new();

    if !branch_name.is_empty() && is_carbon {
        builder.branch(branch_name);
    }

    let carbon_folder = kubejs_dir.join("carbon").join(repo_name);

    let repo = builder.clone(repo_url, temp_dir.path())?;
    builder.clone(repo_url, carbon_folder.as_path())?;

    let mut carbon_files = Vec::new();
    for entry in fs::read_dir(repo.workdir().unwrap())
        .map_err(|e| format!("Failed to read directory: {}", e))?
    {
        if let Some(path) = find_carbon_file(
            entry.map_err(|e| format!("Failed to read entry: {}", e))?,
            name,
            is_carbon,
        ) {
            carbon_files.push(path);
        }
    }

    let mut startup_scripts = Vec::new();
    let mut client_scripts = Vec::new();
    let mut server_scripts = Vec::new();
    let mut assets = Vec::new();
    for entry in fs::read_dir(repo.workdir().unwrap())
        .map_err(|e| format!("Failed to read directory: {}", e))?
    {
        if let Some(path) =
            find_scripts_folder(entry.map_err(|e| format!("Failed to read entry: {}", e))?)
        {
            match path.file_name().unwrap().to_str().unwrap() {
                "startup_scripts" => startup_scripts.push(path),
                "client_scripts" => client_scripts.push(path),
                "server_scripts" => server_scripts.push(path),
                "assets" => assets.push(path),
                _ => {}
            }
        }
    }

    Ok((
        temp_dir.into_path(),
        carbon_files,
        startup_scripts,
        client_scripts,
        server_scripts,
        assets,
    ))
}

pub async fn get_json_from_github(
    repo: &str,
    branch: &str,
    name: &str,
    is_carbon: bool,
) -> Result<(), Box<dyn std::error::Error>> {
    let client = reqwest::Client::builder()
        .user_agent("KubeJsPackageManager (carbonkjs@gmail.com)")
        .build()?;

    let repo_url = format!("https://api.github.com/repos/{}/{}", name, repo);

    let file_name = if is_carbon {
        "carbon.config.json"
    } else {
        ".kjspkg"
    };

    let default_file_url = format!(
        "https://raw.githubusercontent.com/{}/{}/master/{}",
        name, repo, file_name
    );

    let repo_response = client.get(&repo_url).send().await?;

    match repo_response.status() {
        StatusCode::OK => {
            if is_carbon {
                let carbon_config = fetch_carbon_config(default_file_url, client).await?;
                print_carbon_config(carbon_config);
            } else {
                let kjspkg_config = fetch_kjspkg_config(default_file_url, client, name).await?;
                print_kjspkg_config(repo, kjspkg_config);
            }
        }
        StatusCode::NOT_FOUND => {
            println!(
                "[{}] {}",
                "error".red().bold(),
                "This script does not exist. Make sure you typed the name correctly."
            );
            return Err(Box::new(std::io::Error::new(
                std::io::ErrorKind::NotFound,
                "Script not found",
            )));
        }
        _ => {
            println!(
                "[{}] {}",
                "error".red().bold(),
                "Failed to retrieve script info"
            );
            return Err(Box::new(std::io::Error::new(
                std::io::ErrorKind::Other,
                "Failed to retrieve script info",
            )));
        }
    }
    Ok(())
}

async fn fetch_carbon_config(
    default_file_url: String,
    client: reqwest::Client,
) -> Result<Config, Box<dyn std::error::Error>> {
    let response = client.get(&default_file_url).send().await?;
    let response_text = response.text().await?;
    let package: Config = serde_json::from_str(&response_text)?;

    Ok(package)
}

async fn fetch_kjspkg_config(
    default_file_url: String,
    client: reqwest::Client,
    name: &str,
) -> Result<KjspkgConfig, Box<dyn std::error::Error>> {
    let response = client.get(&default_file_url).send().await?;
    let response_text = response.text().await?;
    let package: KjspkgConfig = serde_json::from_str(&response_text)?;

    Ok(package)
}

fn print_kjspkg_config(name: &str, package: KjspkgConfig) {
    println!("{}", "Package Info:".bold().underline());
    println!("Name: {}", name.bright_green());
    println!("Author: {}", package.author.bright_green());
    println!("Description: {}", package.description.bright_green());
    let result = convert_int_to_version(&package.versions);
    println!("Minecraft Version:");
    for (i, version) in result.iter().enumerate() {
        if i > 0 {
            print!(", ");
        }
        print!("{}", version.bright_green());
    }

    println!();
    println!("Modloaders: ");
    for (i, modloader) in package.modloaders.iter().enumerate() {
        if i > 0 {
            print!(", ");
        }
        print!("{}", modloader.bright_green());
    }
    println!();
}

fn print_carbon_config(package: Config) {
    println!("{}", "Package Info:".bold().underline());
    println!("Name: {}", package.name.bright_green());
    println!("Version: {}", package.version.bright_green());
    println!("Author: {}", package.author.bright_green());
    println!("Description: {}", package.description.bright_green());
    println!(
        "Minecraft Version: {}",
        package.minecraftVersion.bright_green()
    );
    println!("Modloaders: ");
    for (i, modloader) in package.modloaders.iter().enumerate() {
        if i > 0 {
            print!(", ");
        }
        print!("{}", modloader.bright_green());
    }
    println!();
}