use crate::{diff, fs::util::*};
use humantime::Duration;
use std::fs;
use std::io;
use std::path::PathBuf;
use std::time::SystemTime;
use std::time::UNIX_EPOCH;

pub fn create_patch(
    repo_dir: &PathBuf,
    data_dir: &PathBuf,
    keep_dur: Duration,
    dry_run: bool,
) -> Result<(), io::Error> {
    let dirs = setup_dirs(repo_dir, Some(data_dir));
    init_lock(repo_dir)?;

    let ts = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_secs();
    let table_path = if !dry_run {
        Some(format!("{}/{}", dirs.patches_path, ts))
    } else {
        None
    };

    let new_paths = get_walk(dirs.data_dir.clone().unwrap(), dirs.ignore_path.clone())?;
    let repo_paths = get_walk(dirs.repo_base.clone(), dirs.ignore_path.clone())?;

    let dir_diffs = get_diffs_dir(
        &new_paths,
        &repo_paths,
        dirs.data_dir.clone().unwrap(),
        dirs.repo_base.clone(),
    );

    let file_diffs = get_diffs_file(
        &new_paths,
        &repo_paths,
        dirs.data_dir.clone().unwrap(),
        dirs.repo_base.clone(),
    );

    let copy_files = diff::new_table(
        file_diffs.clone(),
        &dirs.repo_base,
        &dirs.data_dir.clone().unwrap(),
        table_path,
        dirs.hash_path,
    )?
    .iter()
    .map(|f| PathBuf::from(format!("{}/{}", &dirs.data_dir.clone().unwrap(), f)))
    .collect();

    if !dry_run {
        // make any new dirs
        copy_dir_all(
            &dirs.data_dir.clone().unwrap(),
            &new_paths,
            &dirs.repo_base,
            false,
            true,
        )?;

        // delete old dirs + files
        rm_old(
            &dirs.repo_base,
            dir_diffs
                .1
                .into_iter()
                .map(|(_, p)| p)
                .chain(file_diffs.1.into_iter().map(|(_, p)| p))
                .collect(),
        )?;

        // copy any new or updated files
        copy_dir_all(
            &dirs.data_dir.unwrap(),
            &copy_files,
            &dirs.repo_base,
            true,
            false,
        )?;

        // if patch has lower number than newest - keep { delete }
        let keep = keep_dur.as_secs();
        for patch_path in get_patches(dirs.patches_path) {
            let patch_id = patch_path
                .file_name()
                .unwrap()
                .to_str()
                .unwrap()
                .to_string()
                .parse::<u64>()
                .unwrap();

            // once patch gets higher (newer) than threshold, break
            if patch_id > ts - keep {
                break;
            }

            fs::remove_dir_all(patch_path.to_str().unwrap().to_string())?;
        }
    }

    Ok(())
}
