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

pub fn pull_patch(
    repo_dir: &PathBuf,
    out_dir: &PathBuf,
    target_patch: Timestamp,
    rebase: bool,
) -> Result<(), io::Error> {
    let dirs = setup_dirs(repo_dir, Some(out_dir));
    init_lock(repo_dir)?;

    let base_paths = get_walk(dirs.repo_base.clone(), dirs.ignore_path.clone())?;

    if !rebase {
        fs::create_dir(dirs.data_dir.clone().unwrap())?;
    };

    let mut patches_paths: Vec<String> = vec![];

    let target = target_patch.duration_since(UNIX_EPOCH).unwrap().as_secs();
    for patch_path in get_patches(dirs.patches_path).iter().rev() {
        let patch_id = patch_path
            .file_name()
            .unwrap()
            .to_str()
            .unwrap()
            .to_string()
            .parse::<u64>()
            .unwrap();

        if patch_id < target {
            break;
        }

        patches_paths.push(patch_path.to_str().unwrap().to_string());
    }

    // will create dirs + write files
    let rm_files = diff::apply_entries(
        dirs.repo_base.clone(),
        dirs.data_dir.clone().unwrap(),
        patches_paths.clone(),
    )?;

    let del_files = &rm_files
        .clone()
        .into_iter()
        .map(|f| PathBuf::from(format!("{}/{}", dirs.data_dir.clone().unwrap(), f)))
        .collect();

    let all_files: Vec<PathBuf> = get_diffs_file(
        &base_paths,
        &del_files,
        dirs.repo_base.clone(),
        dirs.data_dir.clone().unwrap(),
    )
    .0
    .into_iter()
    .map(|f| PathBuf::from(format!("{}/{}", dirs.repo_base.clone(), f.1)))
    .collect();

    let full_dirs = get_full_dirs(all_files.clone(), dirs.repo_base.clone());
    let all_paths = &full_dirs.clone().into_iter().chain(all_files).collect();

    if !rebase {
        // copy any files that aren't in entries
        copy_dir_all(
            &dirs.repo_base,
            &all_paths,
            &dirs.data_dir.clone().unwrap(),
            false,
            false,
        )?;
    } else {
        // delete old dirs + files
        rm_old(
            &dirs.repo_base.clone(),
            rm_files
                .into_iter()
                .chain(get_empty_dirs(
                    &base_paths
                        .clone()
                        .into_iter()
                        .filter(|p| p.is_dir())
                        .collect(),
                    &full_dirs,
                    dirs.repo_base.clone(),
                ))
                .collect(),
        )?;

        let repo_paths = get_walk(dirs.repo_base.clone(), dirs.ignore_path.clone())?;
        let hashes = get_hashes(repo_paths)?;
        diff::hash::write_hashes(dirs.hash_path, hashes)?;

        // remove all the patches used to get back to timestamp
        for patch_path in patches_paths {
            fs::remove_dir_all(patch_path)?;
        }
    }

    Ok(())
}

pub fn revert(repo_dir: &PathBuf, target_patch: Timestamp) -> Result<(), io::Error> {
    let dirs = setup_dirs(repo_dir, None);

    pull_patch(
        &PathBuf::from(dirs.repo_dir.clone()),
        &PathBuf::from(dirs.repo_base.clone()),
        target_patch.clone(),
        true,
    )?;

    Ok(())
}
