use futures_util::StreamExt;
use serde::Serialize;
use std::cmp::min;
use std::io::Write;
use std::path::PathBuf;
use tauri::Emitter;

#[derive(Clone, Serialize)]
pub struct DownloadStatus {
    pub downloaded: usize,
    pub total: usize,
    pub time_elapsed: usize,
    pub download_name: String,
}

pub async fn download(
    window: Option<tauri::AppHandle>,
    url: String,
    mut writer: impl Write,
    downloadName: String,
) -> Result<(), String> {
    let client = reqwest::Client::new();
    let res = client
        .get(url)
        .send()
        .await
        .or(Err(format!("Failed to fetch from URL!")))?;
    let total_size = res
        .content_length()
        .ok_or(format!("Failed to get content length"))?;
    let mut downloaded: u64 = 0;
    let mut stream = res.bytes_stream();
    while let Some(item) = stream.next().await {
        let chunk = item.or(Err(format!("Error while downloading file!")))?;
        writer
            .write_all(&chunk)
            .or(Err("Error writing to stream!"))?;
        let new = min(downloaded + (chunk.len() as u64), total_size);
        downloaded = new;
        println!(
            "Downloading {}: {}MB / {}MB",
            downloadName.clone(),
            downloaded / (1024 * 1024),
            total_size / (1024 * 1024)
        );
        if let Some(window) = window.clone() {
            if downloaded != total_size {
                window
                    .emit(
                        "download_progress",
                        DownloadStatus {
                            downloaded: downloaded as usize,
                            total: total_size as usize,
                            time_elapsed: 0,
                            download_name: downloadName.clone(),
                        },
                    )
                    .or(Err(format!("Unable to signal window")))?;
            } else {
                window
                    .emit("download_finished", true)
                    .or(Err(format!("Unable to signal window!")))?;
            }
        }
    }
    return Ok(());
}
