use sha2::{Digest, Sha512};
use std::fs::{remove_dir_all};
use std::io::Cursor;
use std::{
    fs::{File, create_dir_all},
    io::{copy},
    path::PathBuf,
    process::Command
};
use tauri::{AppHandle, Manager};
use tauri_plugin_os::platform;
use zip::ZipArchive;

#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;

fn should_skip(name: &str) -> bool {
    name.starts_with("__MACOSX/")
        || name == "__MACOSX"
        || name.ends_with("/.DS_Store")
        || name.ends_with(".DS_Store")
}

async fn unzip_to_dir(bytes: &[u8], target: &PathBuf) -> String {
    let bytes = bytes.to_vec();
    let target = target.clone();

    let res = tauri::async_runtime::spawn_blocking(move || -> Result<(), String> {
        let reader = Cursor::new(bytes);
        let mut archive = ZipArchive::new(reader).map_err(|e| e.to_string())?;

        for i in 0..archive.len() {
            let mut entry = archive.by_index(i).map_err(|e| e.to_string())?;
            let name = entry.name();

            if should_skip(name) {
                continue;
            }

            let outpath = target.join(name);

            if entry.is_dir() {
                create_dir_all(&outpath).map_err(|e| e.to_string())?;

                #[cfg(unix)]
                if let Some(mode) = entry.unix_mode() {
                    std::fs::set_permissions(&outpath, std::fs::Permissions::from_mode(mode))
                        .map_err(|e| e.to_string())?;
                }
            } else {
                if let Some(parent) = outpath.parent() {
                    create_dir_all(parent).map_err(|e| e.to_string())?;
                }

                let mut outfile = File::create(&outpath).map_err(|e| e.to_string())?;
                copy(&mut entry, &mut outfile).map_err(|e| e.to_string())?;

                #[cfg(unix)]
                if let Some(mode) = entry.unix_mode() {
                    std::fs::set_permissions(&outpath, std::fs::Permissions::from_mode(mode))
                        .map_err(|e| e.to_string())?;
                }
            }
        }

        Ok(())
    })
    .await;

    match res {
        Ok(Ok(())) => "1".into(),
        _ => "-1".into(),
    }
}

fn get_sha512_hash(data: &[u8]) -> String {
    let mut hasher = Sha512::new();
    hasher.update(data);
    let hash = hasher.finalize();
    format!("{:x}", hash)
}

#[tauri::command]
async fn download(app: AppHandle, url: String, hash: String) -> String {
    let client = reqwest::Client::new();
    let resp = match client.get(&url).send().await {
        Ok(r) => r,
        Err(_) => return "-1".to_string(),
    };
    let bytes = match resp.bytes().await {
        Ok(b) => b,
        Err(_) => return "-1".to_string(),
    };

    let download_hash = get_sha512_hash(&bytes);
    if hash != download_hash {
        return "-2".to_string();
    }

    let bin_path = match app.path().app_local_data_dir() {
        Ok(p) => p.join("bin"),
        Err(_) => return "-1".to_string(),
    };
    let _ = tokio::fs::create_dir_all(&bin_path).await;
    let unzip_res = unzip_to_dir(&bytes, &bin_path).await;
    if unzip_res == "-1" {
        return "-1".to_string();
    }

    drop(bytes);

    return "1".to_string();
}

#[allow(unused_variables)]
#[tauri::command]
fn load(app: AppHandle) {
    let bin_path = match app.path().app_local_data_dir() {
        Ok(p) => p.join("bin"),
        Err(_) => return,
    };
    if !bin_path.exists() {
        return;
    }

    if platform() == "macos" {
        if let Err(_) = Command::new("open")
            .arg("Lncvrt Games Launcher.app")
            .current_dir(&bin_path)
            .spawn() {
            eprintln!("Failed to launch game on macOS");
        }
    } else if platform() == "linux" {
        if let Err(_) = Command::new("./lncvrt-games-launcher")
            .current_dir(&bin_path)
            .spawn() {
            eprintln!("Failed to launch game on macOS");
        }
    } else if platform() == "windows" {
        if let Err(_) = Command::new(&bin_path.join("lncvrt-games-launcher.exe"))
            .current_dir(&bin_path)
            .spawn() {
            eprintln!("Failed to launch game on macOS");
        }
    }

    app.exit(0);
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .setup(|app| {
            let app_local_data_dir = match app.path().app_local_data_dir() {
                Ok(p) => p,
                Err(_) => return Ok(()),
            };
            let downloads_dir = app_local_data_dir.join("downloads");
            let updates_dir = app_local_data_dir.join("updates");
            let bin_dir = app_local_data_dir.join("bin");
            let version_file = app_local_data_dir.join(".version");

            if downloads_dir.exists() {
                let _ = remove_dir_all(downloads_dir);
            }
            if updates_dir.exists() {
                let _ = remove_dir_all(&updates_dir);
            }
            if bin_dir.exists() && bin_dir.is_file() {
                let _ = remove_dir_all(&bin_dir);
            }
            if version_file.exists() && !bin_dir.is_file() {
                let _ = remove_dir_all(&version_file);
            }

            Ok(())
        })
        .plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| {
            let _ = app
                .get_webview_window("main")
                .expect("no main window")
                .set_focus();
        }))
        .plugin(tauri_plugin_dialog::init())
        .plugin(tauri_plugin_opener::init())
        .plugin(tauri_plugin_os::init())
        .plugin(tauri_plugin_fs::init())
        .invoke_handler(tauri::generate_handler![download, load])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
