Documentation
use std::collections::HashMap;
use std::error::Error;
use std::fs;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
use std::process::Command;
use include_dir::{Dir, include_dir};
use text_placeholder::Template;
use crate::MyConfig;


static TEMPLATE_DIR: Dir<'_> = include_dir!("src/template");

/**
将模板文件输出到目标目录,并替换文件内容
 */
fn resolve_files(dir: &Dir, conf: &MyConfig) -> Result<(), Box<dyn Error>> {
    let project_name = conf.name.as_ref().unwrap();
    let project_title = conf.title.as_ref().unwrap_or(project_name);
    for entry in dir.entries() {
        if let Some(x) = entry.as_dir() {
            resolve_files(x, conf)?;
        } else {
            let file_path = entry.path();
            let mut output_path_buf = PathBuf::new();
            output_path_buf.push(project_name);
            output_path_buf.push(file_path);
            let output_path = output_path_buf.to_str().unwrap();
            let parent_dir = std::path::Path::new(output_path).parent().unwrap();
            fs::create_dir_all(parent_dir)?;
            let mut file = File::create(output_path)?;
            if let Some(content) = entry.as_file().unwrap().contents_utf8() {
                //替换文字
                let tt = Template::new_with_placeholder(content, "<%", "%>");
                let mut map = HashMap::new();
                map.insert("name", project_name.as_str());
                map.insert("title", project_title.as_str());
                let new_content = tt.fill_with_hashmap(&map);
                file.write_all(new_content.as_bytes())?;
            } else {
                file.write_all(entry.as_file().unwrap().contents())?;
            }
        }
    }
    Ok(())
}

fn init_git(conf: &MyConfig) -> Result<(), Box<dyn Error>> {
    if conf.skip_git {
        return Ok(());
    }
    let project_name = conf.name.as_ref().unwrap();
    //初始化git仓库
    Command::new("git")
        .arg("init")
        .current_dir(&project_name)
        .output()?;
    //添加全部文件
    Command::new("git")
        .arg("add")
        .arg(".")
        .current_dir(&project_name)
        .output()?;
    //提交
    Command::new("git")
        .arg("commit")
        .arg("-m")
        .arg("initial commit")
        .current_dir(&project_name)
        .output()?;
    Ok(())
}

pub fn runner_init(conf: MyConfig) -> Result<(), String> {
    //获取name参数
    let project_name = conf.name.as_ref().unwrap();
    //判断name在本地是否有同名文件夹或文件
    if let Ok(_) = fs::metadata(project_name) {
        return Err(format!("name已经存在: {project_name}"));
    }
    //输出文件
    if let Err(e) = resolve_files(&TEMPLATE_DIR, &conf) {
        return Err(format!("模板文件解析失败: {e}"));
    }
    //初始化git
    if let Err(e) = init_git(&conf) {
        return Err(format!("git初始化失败: {e}"));
    };
    Ok(())
}

#[cfg(test)]
mod tests {
    use uuid::Uuid;
    use crate::{MyCommand, MyConfig};
    use super::*;

    #[cfg(test)]
    #[macro_export]
    macro_rules! assert_contains {
        ($filename:expr, $test_name:expr) => {
            if let Ok(c) = fs::read_to_string($filename) {
                assert!(c.contains($test_name));
            } else {
                assert!(false);
            }
        };
    }

    fn setup(test_name: &str) {
        //检查测试文件夹是否存在,如果存在则先删除
        let result = fs::metadata(&test_name);
        if let Ok(m) = result {
            if m.is_dir() {
                fs::remove_dir_all(&test_name).unwrap();
            } else {
                fs::remove_file(&test_name).unwrap();
            }
        }
    }

    fn teardown(test_name: &str) {
        //删除测试文件夹
        fs::remove_dir_all(test_name).unwrap();
    }

    #[test]
    fn test_runner_init_normal() {
        let test_name = Uuid::new_v4().to_string();
        const TEST_DESC: &str = "我的文档";
        setup(&test_name);
        //初始化参数
        let config = MyConfig {
            command: MyCommand::INIT,
            name: Some(test_name.to_string()),
            title: Some(TEST_DESC.to_string()),
            skip_git: false,
        };
        //执行init命令
        runner_init(config).unwrap();
        //校验是否成功
        assert_contains!(format!("{test_name}/antora.yml"),&test_name);
        assert_contains!(format!("{test_name}/antora-playbook.yml"),&test_name);
        assert_contains!(format!("{test_name}/antora.yml"),TEST_DESC);
        assert_contains!(format!("{test_name}/antora-playbook.yml"),TEST_DESC);
        teardown(&test_name);
    }

    #[test]
    fn test_runner_init_without_desc() {
        let test_name = Uuid::new_v4().to_string();

        setup(&test_name);
        //初始化参数
        let config = MyConfig {
            command: MyCommand::INIT,
            name: Some(test_name.to_string()),
            title: None,
            skip_git: false,
        };
        //执行init命令
        runner_init(config).unwrap();
        //校验是否成功
        assert_contains!(format!("{test_name}/antora.yml"),&test_name);
        assert_contains!(format!("{test_name}/antora-playbook.yml"),&test_name);
        assert_contains!(format!("{test_name}/antora.yml"),&test_name);
        assert_contains!(format!("{test_name}/antora-playbook.yml"),&test_name);
        teardown(&test_name);
    }

    #[test]
    fn test_git_init() {
        let test_name = Uuid::new_v4().to_string();
        setup(&test_name);
        fs::create_dir_all(&test_name).unwrap();
        //初始化参数
        let config = MyConfig {
            command: MyCommand::INIT,
            name: Some(test_name.to_string()),
            title: None,
            skip_git: false,
        };
        init_git(&config).unwrap();
        let git_path = format!("{test_name}/.git");
        assert!(fs::metadata(PathBuf::from(&git_path)).is_ok());
        assert!(fs::metadata(PathBuf::from(format!("{git_path:}/config"))).is_ok());
        assert!(fs::metadata(PathBuf::from(format!("{git_path:}/hooks"))).is_ok());
        teardown(&test_name);
    }

    #[test]
    fn test_skip_git() {
        let test_name = Uuid::new_v4().to_string();
        setup(&test_name);
        fs::create_dir_all(&test_name).unwrap();
        //初始化参数
        let config = MyConfig {
            command: MyCommand::INIT,
            name: Some(test_name.to_string()),
            title: None,
            skip_git: true,
        };
        init_git(&config).unwrap();
        let git_path = format!("{test_name}/.git");
        assert!(fs::metadata(PathBuf::from(&git_path)).is_err());
        assert!(fs::metadata(PathBuf::from(format!("{git_path:}/config"))).is_err());
        assert!(fs::metadata(PathBuf::from(format!("{git_path:}/hooks"))).is_err());
        teardown(&test_name);
    }
}