use std::{
    collections::HashMap,
    fs::FileType,
    path::{Path, PathBuf},
};

use walkdir::WalkDir;

use crate::{config::Config, manifest::Manifest};

#[derive(PartialEq, Eq)]
#[cfg_attr(test, derive(Debug))]
enum FileSystemTree {
    Node(PathBuf),
    Branch(HashMap<String, FileSystemTree>),
}

impl FileSystemTree {
    fn new() -> Self {
        Self::Branch(HashMap::new())
    }

    fn insert(&mut self, target: &Path, source: PathBuf) {
        let mut current = self;
        for subpath in target.components().skip(1) {
            let name = subpath.as_os_str().to_string_lossy().to_string();
            match current {
                Self::Node(_) => {
                    *current = Self::Branch({
                        let mut map = HashMap::new();
                        map.insert(name, Self::new());
                        map
                    });
                }
                Self::Branch(m) => {
                    current = m.entry(name).or_insert(FileSystemTree::new());
                }
            }
        }

        *current = Self::Node(source);
    }
}

pub fn preview(
    config: Config,
    source_dir: PathBuf,
    target_dir: PathBuf,
) -> anyhow::Result<FileSystemTree> {
    let mut tree = FileSystemTree::new();

    for portal in &config.portals {
        let source = source_dir.clone().join(&portal.from);
        let target = target_dir.clone().join(&portal.to);
        if !source.exists() {
            anyhow::bail!("Source path {} does not exist", source.display());
        }

        if source.is_file() {
            tree.insert(&target, source);
        } else if source.is_dir() {
            for entry in WalkDir::new(&source).into_iter().flatten() {
                let a = entry.path().strip_prefix(&source).unwrap();
                tree.insert(&target.join(a), entry.path().to_path_buf());
            }
        }
    }

    Ok(tree)
}

#[cfg(test)]
mod test {
    use std::fs::{File, create_dir_all};

    use crate::config::Portal;

    use super::*;
    use tempfile::{TempDir, tempdir};
    use test_case::test_case;

    fn generate_branch(ls: Vec<(&'static str, FileSystemTree)>) -> FileSystemTree {
        FileSystemTree::Branch({
            let mut map = HashMap::new();
            for (k, v) in ls {
                map.insert(k.to_string(), v);
            }
            map
        })
    }

    #[test_case(|temp|{
        File::create(temp.path().join("file1")).unwrap();
    }, vec![
        Portal {
            from: "file1".to_string(),
            to: "file1".to_string(),
            file_type: None,
        },
    ], |temp|{
        generate_branch(vec![(
            "file1",
            FileSystemTree::Node(temp.path().join("file1")),
        )])
    }; "")]
    fn test_preview<T: FnOnce(&TempDir), F: FnOnce(&TempDir) -> FileSystemTree>(
        create: T,
        portals: Vec<Portal>,
        tree: F,
    ) {
        let temp = tempdir().unwrap();
        create(&temp);

        assert_eq!(
            tree(&temp),
            preview(
                Config {
                    portals,
                    ..Default::default()
                },
                temp.path().to_path_buf(),
                PathBuf::from("/"),
            )
            .unwrap()
        );
    }
}

write me more test case