use owo_colors::{OwoColorize, Style};
use ql_core::{
    Instance, InstanceKind, IntoStringError, ListEntry, Loader, OptifineUniqueVersion, eeprintln,
    err, info,
    json::{InstanceConfigJson, VersionDetails},
};
use ql_mod_manager::loaders::LoaderInstallResult;
use std::{path::PathBuf, process::exit, sync::Arc};

use crate::{
    cli::{QLoader, account::refresh_account, helpers::render_row},
    state::get_entries,
};

use super::PrintCmd;

pub fn list_available_versions(kind: InstanceKind) {
    use std::io::Write;

    eeprintln!("Listing downloadable versions...");
    let (versions, _) = match tokio::runtime::Runtime::new()
        .unwrap()
        .block_on(ql_instances::list_versions())
        .strerr()
    {
        Ok(n) => n,
        Err(err) => {
            panic!("Could not list versions!\n{err}");
        }
    };

    let mut stdout = std::io::stdout().lock();
    for version in versions {
        match kind {
            InstanceKind::Client => {}
            InstanceKind::Server => {
                if !version.supports_server {
                    continue;
                }
            }
        }
        writeln!(stdout, "{version}").unwrap();
    }
}

pub fn list_instances(
    properties: Option<&[String]>,
    kind: InstanceKind,
) -> Result<(), Box<dyn std::error::Error>> {
    use std::fmt::Write;

    let mut cmds: Vec<PrintCmd> = properties
        .unwrap_or_default()
        .iter()
        .filter_map(|n| match n.as_str() {
            "name" => Some(PrintCmd::Name),
            "version" => Some(PrintCmd::Version),
            "loader" => Some(PrintCmd::Loader),
            _ => None,
        })
        .collect();
    if cmds.is_empty() {
        cmds.push(PrintCmd::Name);
    }

    let runtime = tokio::runtime::Runtime::new()?;

    let (instances, _) = tokio::runtime::Runtime::new()?.block_on(get_entries(kind))?;

    let mut cmds_name = String::new();
    let mut cmds_version = String::new();
    let mut cmds_loader = String::new();

    for instance in instances {
        let instance_dir = kind.get_root_directory().join(&instance);
        for cmd in &cmds {
            match cmd {
                PrintCmd::Name => {
                    _ = writeln!(cmds_name, "{}", instance.bold().underline());
                }
                PrintCmd::Version => {
                    match runtime.block_on(VersionDetails::load_from_path(&instance_dir)) {
                        Ok(json) => {
                            cmds_version.push_str(&json.id);
                        }
                        Err(err) => {
                            err!("{err}");
                        }
                    }
                    cmds_version.push('\n');
                }
                PrintCmd::Loader => {
                    let config_json =
                        match runtime.block_on(InstanceConfigJson::read_from_dir(&instance_dir)) {
                            Ok(json) => json,
                            Err(err) => {
                                err!("{err}");
                                cmds_loader.push('\n');
                                continue;
                            }
                        };
                    let m = config_json.mod_type;

                    match m {
                        Loader::Vanilla => writeln!(cmds_loader, "{}", m.bright_black()),
                        Loader::Fabric => writeln!(cmds_loader, "{}", m.bright_green()),
                        Loader::Quilt => writeln!(cmds_loader, "{}", m.bright_purple()),
                        Loader::Forge => writeln!(cmds_loader, "{}", m.bright_yellow()),
                        Loader::Neoforge => writeln!(cmds_loader, "{}", m.yellow()),
                        Loader::OptiFine => writeln!(cmds_loader, "{}", m.red().bold()),
                        Loader::Paper => writeln!(cmds_loader, "{}", m.blue()),
                        Loader::Liteloader => writeln!(cmds_loader, "{}", m.bright_blue()),
                        Loader::Modloader => writeln!(cmds_loader, "{m}"),
                        Loader::Rift => writeln!(cmds_loader, "{}", m.bold().underline()),
                    }?;
                }
            }
        }
    }

    let Some((terminal_size::Width(width), _)) = terminal_size::terminal_size() else {
        println!("{cmds_name}\n\n{cmds_loader}\n\n{cmds_version}");
        return Ok(());
    };

    let cmds: Vec<(String, Option<Style>)> = cmds
        .iter()
        .map(|n| match n {
            PrintCmd::Name => (cmds_name.clone(), None),
            PrintCmd::Version => (cmds_version.clone(), None),
            PrintCmd::Loader => (cmds_loader.clone(), None),
        })
        .collect();

    println!("{}", render_row(width, &cmds, true).unwrap());

    Ok(())
}

pub async fn create_instance(
    instance_name: String,
    version: String,
    skip_assets: bool,
    kind: InstanceKind,
) -> Result<(), Box<dyn std::error::Error>> {
    let entry = ListEntry::new(version);

    match kind {
        InstanceKind::Client => {
            ql_instances::create_instance(instance_name, entry, None, !skip_assets).await?;
        }
        InstanceKind::Server => {
            ql_servers::create_server(instance_name, entry, None).await?;
        }
    }

    Ok(())
}

pub fn delete_instance(
    instance_name: &str,
    force: bool,
    kind: InstanceKind,
) -> Result<(), Box<dyn std::error::Error>> {
    if !force {
        println!(
            "{} {instance_name}?",
            "Are you SURE you want to delete the instance"
                .yellow()
                .bold()
        );
        println!("This can't be undone, you will lose all your data");
        if !confirm_action() {
            println!("Cancelled");
            return Ok(());
        }
    }

    let instance = Instance::new(instance_name, kind);
    let deleted_instance_dir = instance.get_instance_path();
    std::fs::remove_dir_all(&deleted_instance_dir)?;
    info!("Deleted instance {}", instance.get_name());

    Ok(())
}

fn confirm_action() -> bool {
    use std::io::Write;

    print!("[Y/n] ");
    std::io::stdout().flush().unwrap();

    let mut user_input = String::new();
    std::io::stdin().read_line(&mut user_input).unwrap();

    let user_input = user_input.trim().to_lowercase();

    let res = match user_input.as_str() {
        "y" | "yes" | "" => true,
        "n" | "no" => false,
        _ => {
            println!("\nInvalid input. Please respond with 'Y' or 'n'.\n");
            confirm_action() // Retry
        }
    };
    println!();
    res
}

pub async fn launch_instance(
    instance_name: &str,
    username: String,
    use_account: bool,
    kind: InstanceKind,
    show_progress: bool,
    account_type: Option<&str>,
) -> Result<(), Box<dyn std::error::Error>> {
    let account = if matches!(kind, InstanceKind::Client) {
        refresh_account(&username, use_account, show_progress, account_type).await?
    } else {
        None
    };

    let instance_name = Arc::from(instance_name);

    let child = match kind {
        InstanceKind::Client => {
            ql_instances::launch(
                instance_name,
                username,
                None,
                account.clone(),
                None, // No global defaults in CLI mode
                Vec::new(),
            )
            .await?
        }
        // TODO: stdin input
        InstanceKind::Server => ql_servers::run(instance_name, None).await?,
    };

    let mut censors = Vec::new();
    if let Some(token) = account.as_ref().and_then(|n| n.access_token.as_ref()) {
        censors.push(token.clone());
    }

    match child.read_logs(censors, None).await {
        Some(Ok((s, _, diag))) => {
            info!("Game exited with code {s}");
            if let Some(diag) = diag {
                err!("{diag}");
            }
            exit(s.code().unwrap_or_default());
        }
        Some(Err(err)) => Err(err)?,
        None => {}
    }
    Ok(())
}

pub async fn loader(cmd: QLoader, kind: InstanceKind) -> Result<(), Box<dyn std::error::Error>> {
    match cmd {
        QLoader::Info { instance } => {
            let json = InstanceConfigJson::read(&Instance::new(&instance, kind)).await?;
            println!("Kind: {}", json.mod_type);
            if let Some(info) = json.mod_type_info {
                if let Some(version) = info.version {
                    println!("Version: {version}");
                }
                if let Some(backend) = info.backend_implementation {
                    println!("Backend: {backend}");
                }
                if let Some(jar) = info.optifine_jar {
                    println!("OptiFine Installation: {jar}");
                }
            }
        }
        QLoader::Install {
            instance,
            loader,
            more,
            version,
        } => {
            if loader.eq_ignore_ascii_case("vanilla") {
                err!(
                    "Vanilla refers to the base game.\n    Maybe you meant `./quantum_launcher loader uninstall ...`"
                );
                exit(1);
            }
            let Some(loader) = Loader::ALL
                .iter()
                .copied()
                .find(|n| n.to_modrinth_str().eq_ignore_ascii_case(&loader))
            else {
                err!("Invalid loader: {loader}");
                exit(1)
            };

            let instance = Instance::new(&instance, kind);
            let mt = InstanceConfigJson::read(&instance).await?.mod_type;

            if mt == loader {
                err!("{mt} is already installed!");
                exit(0);
            }
            if !(mt == Loader::Vanilla
                || (mt == Loader::Forge && loader == Loader::OptiFine)
                || (mt == Loader::OptiFine && loader == Loader::Forge))
            {
                err!(
                    r"You can't install a loader on top of another loader!
    Did you mean to uninstall the other one first: `./quantum_launcher loader uninstall ...`"
                );
                exit(1);
            }

            match ql_mod_manager::loaders::install_specified_loader(
                instance.clone(),
                loader,
                None,
                version,
            )
            .await?
            {
                LoaderInstallResult::Ok => {}
                LoaderInstallResult::NeedsOptifine => {
                    install_optifine(more, instance).await?;
                }
                LoaderInstallResult::Unsupported => {
                    err!("This loader is unsupported!");
                    exit(1);
                }
            }
        }
        QLoader::Uninstall { instance } => {
            let instance = Instance::new(&instance, kind);
            ql_mod_manager::loaders::uninstall_loader(instance).await?;
        }
    }
    Ok(())
}

async fn install_optifine(
    more: Option<String>,
    instance: Instance,
) -> Result<(), Box<dyn std::error::Error + 'static>> {
    let details = VersionDetails::load(&instance).await?;
    if details.get_id() == "b1.7.3" {
        ql_mod_manager::loaders::optifine::install_b173(
            instance,
            OptifineUniqueVersion::B1_7_3.get_url().0,
        )
        .await?;
        return Ok(());
    }

    let Some(more) = more else {
        err!(
            r"Please download the OptiFine installer at: https://optifine.net/downloads
    and pass the path via: `quantum_launcher loader install optifine path/to/installer.jar`"
        );
        exit(1);
    };

    ql_mod_manager::loaders::optifine::install(
        instance,
        PathBuf::from(more),
        None,
        None,
        OptifineUniqueVersion::from_version(details.get_id()),
    )
    .await?;
    Ok(())
}
