use std::fs;
use std::path::Path;
use std::time::SystemTime;

use super::error::Result;

pub fn sync_dirs(src: &Path, dst: &Path) -> Result<()> {
    if !src.exists() {
        fs::create_dir_all(src)?;
    }
    if !dst.exists() {
        fs::create_dir_all(dst)?;
    }

    let src_entries = collect_entries(src)?;
    let dst_entries = collect_entries(dst)?;

    for (name, src_meta) in &src_entries {
        let dst_path = dst.join(name);
        match dst_entries.get(name) {
            Some(dst_meta) => {
                if is_newer(&src_meta.modified, &dst_meta.modified) {
                    fs::copy(src.join(name), &dst_path)?;
                } else if is_newer(&dst_meta.modified, &src_meta.modified) {
                    fs::copy(&dst_path, src.join(name))?;
                }
            }
            None => {
                fs::copy(src.join(name), &dst_path)?;
            }
        }
    }

    for (name, _) in &dst_entries {
        if !src_entries.contains_key(name) {
            fs::copy(dst.join(name), src.join(name))?;
        }
    }

    Ok(())
}

struct FileMeta {
    #[allow(dead_code)]
    modified: SystemTime,
}

fn collect_entries(dir: &Path) -> Result<std::collections::HashMap<String, FileMeta>> {
    let mut entries = std::collections::HashMap::new();
    for entry in fs::read_dir(dir)? {
        let entry = entry?;
        let path = entry.path();
        if path.is_file() {
            let name = entry.file_name().to_string_lossy().to_string();
            let meta = entry.metadata()?;
            entries.insert(name, FileMeta {
                modified: meta.modified()?,
            });
        }
    }
    Ok(entries)
}

fn is_newer(a: &SystemTime, b: &SystemTime) -> bool {
    match a.duration_since(*b) {
        Ok(d) => d.as_millis() > 1,
        Err(_) => false,
    }
}
