Documentation
use crate::file::archive::{
    decrypt_zip_bytes, detect_archive_hint, is_encrypted_zip, resolve_output_dir,
    resolve_output_path, unzip_single_from_bytes, unzip_to_dir, write_temp_zip, ArchiveHint,
    MAX_FILE_SIZE,
};
use crate::file::{ContentType, DownloadResponse};
use anyhow::{Context, Result};
use dialoguer::Input;
use indicatif::{ProgressBar, ProgressStyle};
use log::info;
use std::{fs, io::Read, path::Path};

pub fn get_file(server: &str, token: &str, output: Option<&Path>, key: Option<&str>) -> Result<()> {
    let client = reqwest::blocking::Client::new();
    let url = format!("{}/download/{}", normalize_server(server), token);
    let response = client
        .get(&url)
        .send()
        .context("Failed to send download request")?;

    if !response.status().is_success() {
        return Err(anyhow::anyhow!(
            "Download failed: {}",
            response.status()
        ));
    }

    let download_resp: DownloadResponse = response
        .json()
        .context("Failed to parse download response")?;

    match download_resp.content_type {
        ContentType::Text => {
            let content = download_resp
                .content
                .context("No content in response (is this a file?)")?;
            if content.len() as u64 > MAX_FILE_SIZE {
                return Err(anyhow::anyhow!("Message exceeds {}MB limit", MAX_FILE_SIZE / 1024 / 1024));
            }
            println!("{}", content);
        }
        ContentType::File => {
            let file_url = download_resp
                .url
                .context("No url in response (is this a text?)")?;
            let filename = download_resp
                .filename
                .unwrap_or_else(|| "file.bin".to_string());

            let mut file_response = client
                .get(&file_url)
                .send()
                .context("Failed to download file from storage")?;

            if !file_response.status().is_success() {
                return Err(anyhow::anyhow!(
                    "File download failed: {}",
                    file_response.status()
                ));
            }

            let total_size = file_response.content_length();
            let mut bytes: Vec<u8> = Vec::new();
            let mut downloaded: u64 = 0;

            let progress = match total_size {
                Some(total) if total > 0 => {
                    let pb = ProgressBar::new(total);
                    let style = ProgressStyle::with_template(
                        "{msg} {spinner:.green} {bytes}/{total_bytes} ({percent}%) [{bar:40.cyan/blue}] {eta}",
                    )
                    .unwrap()
                    .progress_chars("=>-");
                    pb.set_style(style);
                    pb.set_message(filename.clone());
                    pb
                }
                _ => {
                    let pb = ProgressBar::new_spinner();
                    pb.set_style(
                        ProgressStyle::with_template("{msg} {spinner:.green} {bytes} downloaded")
                            .unwrap(),
                    );
                    pb.set_message(filename.clone());
                    pb.enable_steady_tick(std::time::Duration::from_millis(120));
                    pb
                }
            };

            let mut buffer = [0u8; 64 * 1024];
            loop {
                let read = file_response
                    .read(&mut buffer)
                    .context("Failed to read file response")?;
                if read == 0 {
                    break;
                }
                bytes.extend_from_slice(&buffer[..read]);
                downloaded += read as u64;
                progress.inc(read as u64);

                if downloaded > MAX_FILE_SIZE {
                    progress.finish_and_clear();
                    return Err(anyhow::anyhow!(
                        "File exceeds {}MB limit",
                        MAX_FILE_SIZE / 1024 / 1024
                    ));
                }
            }

            progress.finish_and_clear();

            let (clean_name, hint) = detect_archive_hint(&filename);
            let looks_like_zip = filename.ends_with(".zip")
                || hint != ArchiveHint::None
                || is_encrypted_zip(&bytes)
                || bytes.starts_with(b"PK\x03\x04");

            if looks_like_zip {
                match hint {
                    ArchiveHint::File => {
                        let output_path = resolve_output_path(output, &clean_name);
                        handle_zip_download(&bytes, key, &output_path, ArchiveHint::File)?;
                        info!("Download success: {}", output_path.display());
                    }
                    ArchiveHint::Dir | ArchiveHint::None => {
                        let output_dir = resolve_output_dir(output, &clean_name)?;
                        handle_zip_download(&bytes, key, &output_dir, ArchiveHint::Dir)?;
                        info!("Download success: {}", output_dir.display());
                    }
                }
            } else {
                let output_path = resolve_output_path(output, &filename);
                if let Some(parent) = output_path.parent()
                    && !parent.as_os_str().is_empty()
                {
                    fs::create_dir_all(parent).with_context(|| {
                        format!("Failed to create directory: {}", parent.display())
                    })?;
                }
                fs::write(&output_path, &bytes)
                    .with_context(|| format!("Failed to write file: {}", output_path.display()))?;

                info!(
                    "Download success: {} ({} bytes)",
                    output_path.display(),
                    bytes.len()
                );
            }
        }
    }

    Ok(())
}

fn handle_zip_download(
    bytes: &[u8],
    key: Option<&str>,
    output_path: &Path,
    hint: ArchiveHint,
) -> Result<()> {
    if let Some(key) = key {
        if key.trim().is_empty() {
            return Err(anyhow::anyhow!("Decryption key cannot be empty"));
        }
        let decrypted = if is_encrypted_zip(bytes) {
            decrypt_zip_bytes(bytes, key)?
        } else {
            bytes.to_vec()
        };
        return unzip_from_bytes(&decrypted, output_path, hint);
    }

    let unzip_result = unzip_from_bytes(bytes, output_path, hint);
    match unzip_result {
        Ok(()) => Ok(()),
        Err(err) => {
            if !is_encrypted_zip(bytes) {
                return Err(err);
            }
            let prompt = "Enter key";
            let input_key = Input::<String>::new()
                .with_prompt(prompt)
                .allow_empty(true)
                .interact()
                .context("Failed to read key")?;
            let input_key = input_key.trim();
            if input_key.is_empty() {
                return Err(err);
            }
            let decrypted = decrypt_zip_bytes(bytes, input_key)?;
            unzip_from_bytes(&decrypted, output_path, hint)
        }
    }
}

fn unzip_from_bytes(bytes: &[u8], output_path: &Path, hint: ArchiveHint) -> Result<()> {
    if hint == ArchiveHint::File {
        return unzip_single_from_bytes(bytes, output_path);
    }
    let temp_path = write_temp_zip(bytes)?;
    let unzip_result = unzip_to_dir(&temp_path, output_path);
    let _ = fs::remove_file(&temp_path);
    unzip_result
}

fn normalize_server(server: &str) -> String {
    server.trim_end_matches('/').to_string()
}