use std::fs::File;
use std::io::Cursor;
use std::io::Read;
use std::io::Seek;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;

use crate::modpack;
use crate::modpack::get_modpacks;
use crate::modpack::get_versions;
use crate::modpack::VersionEntry;
use crate::sftp;
use chrono;
use serde::Serialize;
use ssh2::Session;
use zip::write::SimpleFileOptions;
use zip::ZipArchive;
use zip::ZipWriter;
use tauri::Emitter;

//static USERNAME: parking_lot::Mutex<String> = parking_lot::const_mutex(String::new());
//static PASSWORD: parking_lot::Mutex<String> = parking_lot::const_mutex(String::new());
static SESSION: tauri::async_runtime::Mutex<Option<Session>> =
    tauri::async_runtime::Mutex::const_new(None);

pub fn emit(event: &str, payload: impl Serialize + Clone, window: tauri::AppHandle) {
    if !window.emit(event, payload).is_ok() {
        println!("Failed to emit to window!");
    }
}

#[tauri::command]
pub async fn login(username: String, password: String, window: tauri::AppHandle) {
    let res = sftp::connect(username, password);
    if res.is_ok() {
        //*USERNAME.lock() = username;
        //*PASSWORD.lock() = password;
        *SESSION.lock().await = Some(res.unwrap());
        emit("Login_Success", {}, window);
    } else {
        emit("Login_Failed", {}, window);
    }
}

#[tauri::command]
pub async fn drop_session() {
    let ref mut session = *SESSION.lock().await;
    if let Some(session) = session {
        session.disconnect(None, "disconnecting", None).unwrap();
    }
    *session = None;
}

async fn update_modpacks(modpacks: Vec<modpack::ModpackEntry>) -> Result<(), String> {
    let data = serde_json::to_string(&modpacks).or(Err("Unable to serialize json"))?;
    let reader = Cursor::new(data.as_bytes());
    let ref mut session = *SESSION.lock().await;
    if let Some(session) = session {
        sftp::uplaod(
            None,
            session.clone(),
            PathBuf::from("/ftp/modpacks.json"),
            reader,
            format!("modpacks.json"),
            data.as_bytes().len(),
        )
        .await?;
    }
    Err(format!("Session doesnt exist?"))
}

async fn update_versions(id: String, versions: Vec<modpack::VersionEntry>) -> Result<(), String> {
    let data = serde_json::to_string(&versions).or(Err("Unable to serialize json"))?;
    let reader = Cursor::new(data.as_bytes());
    let ref mut session = *SESSION.lock().await;
    if let Some(session) = session {
        sftp::uplaod(
            None,
            session.clone(),
            PathBuf::from(format!("/ftp/{}/versions.json", id)),
            reader,
            format!("modpacks.json"),
            data.as_bytes().len(),
        )
        .await?;
    }
    Err(format!("Session doesnt exist?"))
}

#[tauri::command]
pub async fn shift_up(id: String, window: tauri::AppHandle) {
    let mut modpacks = modpack::get_modpacks().await;
    let mut index = 0;
    for pack in modpacks.as_slice() {
        if pack.id == id {
            break;
        }
        index += 1;
    }
    if index != 0 {
        modpacks.swap(index, index - 1);
    }
    let res = update_modpacks(modpacks).await;
    if !res.is_ok() {
        emit("Error", res.unwrap_err(), window);
    }
}

#[tauri::command]
pub async fn shift_down(id: String, window: tauri::AppHandle) {
    let mut modpacks = modpack::get_modpacks().await;
    let mut index = 0;
    for pack in modpacks.as_slice() {
        if pack.id == id {
            break;
        }
        index += 1;
    }
    if index != modpacks.len() - 1 {
        modpacks.swap(index, index + 1);
    }
    let res = update_modpacks(modpacks).await;
    if !res.is_ok() {
        emit("Error", res.unwrap_err(), window);
    }
}

#[tauri::command]
pub async fn add_pack(id: String, name: String, window: tauri::AppHandle) {
    {
        let ref mut session = *SESSION.lock().await;
        if let Some(session) = session {
            let res = sftp::mkdir(session.clone(), PathBuf::from(format!("/ftp/{}", id))).await;
            if !res.is_ok() {
                emit("Error", res.unwrap_err(), window.clone());
            }
            let res = sftp::mkdir(
                session.clone(),
                PathBuf::from(format!("/ftp/{}/Versions", id)),
            )
            .await;
            if !res.is_ok() {
                emit("Error", res.unwrap_err(), window.clone());
            }
        }
    }
    let versions: Vec<VersionEntry> = Vec::new();
    let res = update_versions(id.clone(), versions).await;
    if !res.is_ok() {
        emit("Error", res.unwrap_err(), window.clone());
    }
    let mut modpacks = get_modpacks().await;
    modpacks.push(modpack::ModpackEntry {
        id: id,
        name: name,
        last_updated: format!("{:?}", chrono::offset::Utc::now()),
    });
    let res = update_modpacks(modpacks).await;
    if !res.is_ok() {
        emit("Error", res.unwrap_err(), window);
    }
}

#[tauri::command]
pub async fn remove_pack(id: String, window: tauri::AppHandle) {
    let mut modpacks = get_modpacks().await;
    let mut index = 0;
    for pack in modpacks.clone() {
        if pack.id == id {
            modpacks.remove(index);
            break;
        }
        index += 1;
    }
    let res = update_modpacks(modpacks).await;
    if !res.is_ok() {
        emit("Error", res.unwrap_err(), window);
    }
    {
        let ref mut session = *SESSION.lock().await;
        if let Some(session) = session {
            sftp::rmdir(session.clone(), PathBuf::from(format!("/ftp/{}", id))).await;
        }
    }
}

#[tauri::command]
pub async fn update_pack(
    window: tauri::AppHandle,
    id: String,
    path: String,
    version: String,
) -> Result<(), String> {
    println!(
        "Update modpack {}, to version {}, from file {}",
        id, version, path
    );
    let file = File::open(Path::new(path.as_str())).or(Err(format!("Unable to open file")))?;
    let mut archive = ZipArchive::new(file).or(Err(format!("File not a zip archive!")))?;
    let mut buf = Cursor::new(vec![]);
    let mut out_archive = ZipWriter::new(&mut buf);

    for i in 0..archive.len() {
        let mut file = archive
            .by_index(i)
            .or(Err(format!("error reading archive")))?;
        if file.name() == "overrides/version.txt" {
            continue;
        }
        let res = out_archive.start_file(
            file.name(),
            SimpleFileOptions::default().compression_method(zip::CompressionMethod::Deflated),
        );
        if !res.is_ok() {
            emit("Error", format!("Unable to start zip archive"), window);
            return Err(format!("Unable to start zip archive"));
        }
        let res = std::io::copy(&mut file, &mut out_archive);
        if !res.is_ok() {
            emit("Error", format!("Unable to copy archive to ram"), window);
            return Err(format!("Unable to copy archive to ram"));
        }
    }
    let res = out_archive.start_file(
        "overrides/version.txt",
        SimpleFileOptions::default().compression_method(zip::CompressionMethod::Deflated),
    );
    if !res.is_ok() {
        emit("Error", format!("Unable to create version file"), window);
        return Err(format!("Unable to create version file"));
    }
    let res = out_archive.write_all(version.as_bytes());
    if !res.is_ok() {
        emit("Error", format!("Unable to write to zip"), window);
        return Err(format!("Unable to write to zip"));
    }
    let res = out_archive.finish();
    if !res.is_ok() {
        emit("Error", format!("Unable to finish zip"), window);
        return Err(format!("Unable to finish zip"));
    }
    buf.rewind().unwrap();
    let timestamp = format!("{:?}", chrono::offset::Utc::now());
    let path = format!("Versions/{}-{}.mrpack", id, timestamp);
    {
        let ref mut session = *SESSION.lock().await;
        if let Some(session) = session {
            let size = buf.clone().bytes().count();
            let upload_path = format!("/ftp/{}/{}", id, path.clone());
            println!("Uploading to {}", upload_path);
            let res = sftp::uplaod(
                Some(window.clone()),
                session.clone(),
                PathBuf::from(upload_path),
                &mut buf,
                path.clone(),
                size,
            )
            .await;
            if !res.is_ok() {
                emit("Error", res.clone().unwrap_err(), window.clone());
                return res;
            }
        }
    }

    let mut versions = get_versions(id.clone()).await?;
    versions.push(VersionEntry {
        Version: version,
        Date: timestamp.clone(),
        File: path,
    });
    let res = update_versions(id.clone(), versions).await;
    if !res.is_ok() {
        emit("Error", res.clone().unwrap_err(), window.clone());
        return res;
    }
    let mut modpacks = get_modpacks().await;
    let mut index = 0;
    for pack in modpacks.as_slice() {
        if pack.id == id {
            modpacks[index].last_updated = timestamp;
            break;
        }
        index += 1;
    }
    let res = update_modpacks(modpacks).await;
    if !res.is_ok() {
        emit("Error", res.clone().unwrap_err(), window.clone());
        return res;
    }

    Ok(())
}
