Documentation
use crate::file::archive::{compress_path, encrypt_zip_file, MAX_FILE_SIZE};
use crate::file::UploadResponse;
use anyhow::{Context, Result};
use log::info;
use qiniu_sdk::upload::{AutoUploader, AutoUploaderObjectParams, UploadManager, UploadTokenSigner};
use qiniu_upload_token::StaticUploadTokenProvider;
use std::{
    fs,
    path::{Path, PathBuf},
    sync::{
        atomic::{AtomicBool, Ordering},
        Arc,
    },
    thread,
    time::{Duration, Instant},
};

pub fn send_file(
    server: &str,
    path: Option<&Path>,
    download_limit: u8,
    message: Option<&str>,
    key: Option<&str>,
) -> Result<()> {
    let _ = download_limit;
    let client = reqwest::blocking::Client::new();
    let server = normalize_server(server);

    if let Some(text) = message {
        return send_message(&client, &server, text);
    }

    send_archive(&client, &server, path, key)
}

fn send_message(client: &reqwest::blocking::Client, server: &str, text: &str) -> Result<()> {
    let trimmed = text.trim();
    if trimmed.is_empty() {
        return Err(anyhow::anyhow!("Message cannot be empty"));
    }
    let data = trimmed.as_bytes().to_vec();
    if data.len() as u64 > MAX_FILE_SIZE {
        return Err(anyhow::anyhow!("Message exceeds {}MB limit", MAX_FILE_SIZE / 1024 / 1024));
    }

    let url = format!("{}/upload", server);
    let response = client
        .post(&url)
        .header("x-upload-type", "text")
        .body(trimmed.to_string())
        .send()
        .context("Failed to send text upload request")?;

    if response.status().is_success() {
        let upload_resp: UploadResponse = response
            .json()
            .context("Failed to parse upload response")?;
        info!("Upload success: id={}", upload_resp.id);
        println!("xtool file get {}", upload_resp.id);
        return Ok(());
    }

    Err(anyhow::anyhow!("Upload text failed: {}", response.status()))
}

fn send_archive(
    client: &reqwest::blocking::Client,
    server: &str,
    path: Option<&Path>,
    key: Option<&str>,
) -> Result<()> {
    let (file_path, filename, temp_path) = resolve_upload_target(path)?;
    let result = (|| {
        maybe_encrypt(&file_path, key)?;
        let (upload_token, id) = request_file_upload(client, server, &filename)?;
        upload_to_qiniu(&file_path, &filename, &upload_token)?;
        info!("Upload success: id={}, name={}", id, filename);
        println!("xtool file get {}", id);
        Ok(())
    })();

    if let Some(path) = temp_path {
        let _ = fs::remove_file(path);
    }

    result
}

fn maybe_encrypt(file_path: &Path, key: Option<&str>) -> Result<()> {
    let Some(key) = key else { return Ok(()); };
    if key.trim().is_empty() {
        return Err(anyhow::anyhow!("Encryption key cannot be empty"));
    }
    let encrypted_size = encrypt_zip_file(file_path, key)?;
    if encrypted_size > MAX_FILE_SIZE {
        return Err(anyhow::anyhow!(
            "Encrypted file exceeds {}MB limit",
            MAX_FILE_SIZE / 1024 / 1024
        ));
    }
    Ok(())
}

fn resolve_upload_target(path: Option<&Path>) -> Result<(PathBuf, String, Option<PathBuf>)> {
    let path = path.ok_or_else(|| {
        anyhow::anyhow!("Please provide a file/dir path or -m <message>")
    })?;

    if path.is_dir() {
        eprintln!("Compressing directory: {}", path.display());
    } else {
        eprintln!("Compressing file: {}", path.display());
    }

    let (zip_path, zip_name, size) = compress_path(path)?;

    if size > MAX_FILE_SIZE {
        let _ = fs::remove_file(&zip_path);
        return Err(anyhow::anyhow!(
            "Compressed file exceeds {}MB limit (current: {:.2}MB)",
            MAX_FILE_SIZE / 1024 / 1024,
            size as f64 / 1024.0 / 1024.0
        ));
    }

    Ok((zip_path.clone(), zip_name, Some(zip_path)))
}

fn request_file_upload(
    client: &reqwest::blocking::Client,
    server: &str,
    filename: &str,
) -> Result<(String, String)> {
    let url = format!("{}/upload", server);
    let response = client
        .post(&url)
        .header("x-upload-type", "file")
        .header("x-filename", filename)
        .send()
        .context("Failed to request upload token")?;

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

    let upload_resp: UploadResponse = response
        .json()
        .context("Failed to parse upload response")?;
    let token = upload_resp
        .upload_token
        .context("Missing upload token")?;
    Ok((token, upload_resp.id))
}

fn upload_to_qiniu(file_path: &Path, filename: &str, token: &str) -> Result<()> {
    let running = Arc::new(AtomicBool::new(true));
    let timer_flag = Arc::clone(&running);
    let start = Instant::now();
    let timer_handle = thread::spawn(move || {
        let mut seconds = 0u64;
        while timer_flag.load(Ordering::Relaxed) {
            thread::sleep(Duration::from_secs(1));
            seconds += 1;
            eprintln!("Uploading... elapsed {}s", seconds);
        }
    });

    let token_provider: StaticUploadTokenProvider = token
        .parse()
        .context("Failed to parse upload token")?;
    let upload_manager = UploadManager::builder(UploadTokenSigner::new_upload_token_provider(
        token_provider,
    ))
    .build();
    let uploader: AutoUploader = upload_manager.auto_uploader();

    let params = AutoUploaderObjectParams::builder()
        .file_name(filename)
        .build();

    uploader
        .upload_path(file_path, params)
        .context("Qiniu upload failed")?;

    running.store(false, Ordering::Relaxed);
    let _ = timer_handle.join();
    eprintln!("Upload finished in {:.2}s", start.elapsed().as_secs_f64());
    Ok(())
}

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