use chrono::{prelude::*, Duration};
use clap::{Parser, Subcommand};
use std::path::PathBuf;

/// A simple trash utility
#[derive(Parser)]
#[command(name = "trash")]
#[command(about = "A simple trash utility", long_about = None)]
struct Cli {
    #[clap(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    /// Lists trashed files
    List { path: Option<PathBuf> },
    /// Put a file into the trash
    Put { path: Vec<PathBuf> },
    /// Restore a file by its id
    Restore { id: PathBuf },
    /// Prune files older than n days (defaults to 7)
    Prune { age: Option<u64> },
}

fn list<P: Fn(&trash::TrashItem) -> bool>(predicate: P) -> Vec<trash::TrashItem> {
    let trashed = trash::os_limited::list().unwrap();
    trashed.into_iter().filter(predicate).collect()
}

fn print_list(path: Option<PathBuf>) {
    let predicate: Box<dyn Fn(&trash::TrashItem) -> bool> = match &path {
        Some(p) => Box::new(|item| {
            let orig = match item.original_parent.canonicalize() {
                Ok(p) => p,
                Err(_) => item.original_parent.clone(),
            };
            let pc = p.canonicalize();
            match pc {
                Ok(pc) => pc == orig,
                Err(_) => false,
            }
        }),
        None => Box::new(|_| true),
    };

    for item in list(predicate.as_ref()) {
        println!(
            "{}\0{}\0{}",
            item.original_path().display(),
            item.id.into_string().unwrap(),
            Local
                .timestamp_opt(item.time_deleted, 0)
                .unwrap()
                .format("%Y-%m-%d %H:%M:%S"),
        );
    }
}

fn put(path: Vec<PathBuf>) {
    trash::delete_all(path).unwrap()
}

fn restore(id: PathBuf) {
    let items: Vec<trash::TrashItem> = list(|_| true).into_iter().filter(|i| i.id == id).collect();

    match items.len() {
        0 => panic!("No trashed file with that id"),
        1 => (),
        _ => panic!("Multiple trashed files with that id"),
    }

    trash::os_limited::restore_all(items).unwrap()
}

fn prune(max_days: Option<u64>) {
    let cutoff = { Utc::now() - Duration::days(max_days.unwrap_or(7) as i64) };
    let predicate =
        |item: &trash::TrashItem| Utc.timestamp_opt(item.time_deleted, 0).unwrap() < cutoff;

    let items: Vec<trash::TrashItem> = list(predicate).into_iter().collect();

    trash::os_limited::purge_all(items).unwrap()
}

fn main() {
    let args = Cli::parse();

    match args.command {
        Commands::List { path } => print_list(path),
        Commands::Put { path } => put(path),
        Commands::Restore { id } => restore(id),
        Commands::Prune { age } => prune(age),
    }
}