use std::{
    fs::{create_dir_all, OpenOptions},
    io::Write,
    path::PathBuf,
};
pub fn get_mf_fixture_dir_for(path_name: &str) -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .join("../vendor/microformats-test/tests")
        .join(path_name)
}

fn generate_parameterized_tests() {
    if PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .join("../vendor")
        .exists()
    {
        // Instead of reading the suites directly from disk, we load it like this to make it easier to
        // toggle which ones we'd like to enable.
        // TODO: Enable v1 when support for it is underway.
        // TODO: Enable mixed when both prior versions are passing.
        let suites = [
            "microformats-v2/h-adr",
            "microformats-v2/h-card",
            "microformats-v2/h-entry",
            "microformats-v2/h-event",
            "microformats-v2/h-feed",
            "microformats-v2/h-geo",
            "microformats-v2/h-product",
            "microformats-v2/h-recipe",
            "microformats-v2/h-resume",
            "microformats-v2/h-review",
            "microformats-v2/h-review-aggregate",
            // "microformats-v2/mixed",
            "microformats-v2/rel",
        ];
        let ignored_tests: Vec<_> =
            include_str!("src/parse/test/suite/ignored_generated_tests.txt")
                .split('\n')
                .collect();
        let test_files = suites
            .iter()
            .map(|suite| {
                let fixtures = collect_fixtures_for(suite);
                let (suite_dir, suite_name) = suite.split_once('/').unwrap();
                let safe_suite_name = suite_name.replace('-', "_");

                let suite_path = PathBuf::from(format!(
                    "{}/src/parse/test/suite/generated/{}",
                    env!("CARGO_MANIFEST_DIR"),
                    suite_dir.replace('-', "_")
                ));

                if !suite_path.exists() {
                    create_dir_all(suite_path.clone()).unwrap_or_else(|_| {
                        panic!(
                            "failed to create the recursive directory {:?} for the test suite {:?}",
                            suite_path, suite_name
                        )
                    });
                }

                let test_file_location = suite_path.join(format!("{}.rs", safe_suite_name));

                let mut f = OpenOptions::new()
                    .create(true)
                    .truncate(true)
                    .write(true)
                    .open(&test_file_location)
                    .unwrap_or_else(|_| {
                        panic!(
                            "Could not create the file for the test {} at {:?}.",
                            safe_suite_name, test_file_location
                        )
                    });

                let test_header = format!(
                    "// Tests for {suite_name}
#![allow(non_snake_case, unused_imports)]
use crate::parse::test::suite::check_fixture_for_parser;

",
                    suite_name = suite
                )
                .to_string();

                let test_contents = fixtures
                    .into_iter()
                    .filter(|test_case| !ignored_tests.contains(&test_case.as_str()))
                    .map(|test_name| {
                        let safe_test_name = test_name
                            .replace((suite.to_string() + "/").as_str(), "")
                            .replace('/', "__")
                            .replace('-', "_");
                        format!(
                            r#"
#[tracing_test::traced_test]
#[test]
fn {name}() {{
    check_fixture_for_parser("{fixture_name}");
}}"#,
                            name = safe_test_name,
                            fixture_name = test_name
                        )
                    })
                    .collect::<Vec<_>>()
                    .join("\n")
                    .trim()
                    .to_string();

                write!(f, "{}{}", test_header, test_contents).expect("Failed to write test file.");
                test_file_location
            })
            .collect::<Vec<_>>();

        let generated_test_module_file = PathBuf::from(format!(
            "{}/src/parse/test/suite/generated/mod.rs",
            env!("CARGO_MANIFEST_DIR")
        ));

        let version_mods = test_files
            .into_iter()
            .filter_map(|test_file_path| {
                let file_name = test_file_path
                    .file_name()
                    .and_then(|os| os.to_str())
                    .map(|t| t.to_string());
                let dir_name = test_file_path
                    .parent()
                    .and_then(|p| p.file_name())
                    .and_then(|os| os.to_str())
                    .map(|s| s.to_string());

                file_name.and_then(|fin| dir_name.map(|dn| (dn, fin)))
            })
            .map(|(dir_name, file_name)| {
                (dir_name, file_name.replace('-', "_").replace(".rs", ";"))
            })
            .map(|(version_name, suite_name)| {
                let version_mod_file = format!(
                    "{}/src/parse/test/suite/generated/{}/mod.rs",
                    env!("CARGO_MANIFEST_DIR"),
                    version_name
                );

                if !PathBuf::from(version_mod_file.clone()).exists() {
                    std::fs::write(&version_mod_file, "").expect("cannot create mod file");
                }

                if !String::from_utf8_lossy(&std::fs::read(version_mod_file.clone()).unwrap())
                    .contains(&suite_name)
                {
                    let mut f = OpenOptions::new()
                        .create(true)
                        .append(true)
                        .open(version_mod_file.clone())
                        .unwrap();
                    writeln!(f, "mod {}", suite_name)
                        .expect("Failed to add suite into version folder");
                }

                version_name
            })
            .fold(vec![], |mut acc, value| {
                let mod_line = format!("mod {};", value);
                if !acc.contains(&mod_line) {
                    acc.push(mod_line);
                }

                acc
            })
            .join("\n");

        std::fs::write(generated_test_module_file.clone(), version_mods).unwrap_or_else(|_| {
            panic!(
                "failed to write out the module at {:?}",
                generated_test_module_file
            )
        });
    }
}

fn main() {
    generate_parameterized_tests();
}

fn collect_fixtures_for(suite: &str) -> Vec<String> {
    get_mf_fixture_dir_for(suite)
        .read_dir()
        .unwrap_or_else(|_| panic!("Couldn't find fixtures for testing '{}' support.", suite))
        .filter_map(|file_path| file_path.ok())
        .filter_map(|file_path| {
            file_path
                .path()
                .extension()
                .and_then(|s| s.to_str())
                .map(|s| s.to_string())
                .and_then(|f| {
                    if f == *"json" {
                        Some(file_path.path().to_owned())
                    } else {
                        None
                    }
                })
        })
        .map(|whole_path| {
            let file_name = whole_path
                .clone()
                .file_stem()
                .and_then(|s| s.to_str())
                .map(|s| s.to_string())
                .unwrap_or_default();

            let test_name = format!("{}/{}", suite, file_name);
            test_name
        })
        .collect()
}
