use std::path::Path;
use yaml_serde::Value;

use crate::spec_parser::{RouteConfig, extract_routes};

pub fn load(path: &Path) -> Value {
    let text = std::fs::read_to_string(path)
        .unwrap_or_else(|e| panic!("{} should be readable: {e}", path.display()));
    yaml_serde::from_str(&text)
        .unwrap_or_else(|e| panic!("{} should contain valid YAML or JSON: {e}", path.display()))
}

pub fn load_dir(path: &Path) -> Vec<RouteConfig> {
    let files: Vec<std::path::PathBuf> = std::fs::read_dir(path)
        .expect("specs directory should be readable")
        .map(|e| e.expect("directory entry should be readable").path())
        .filter(|p| p.is_file())
        .collect();

    assert!(!files.is_empty(), "specs directory should contain files");

    load_all(&files)
}

pub fn load_all(paths: &[std::path::PathBuf]) -> Vec<RouteConfig> {
    let handles: Vec<_> = paths
        .iter()
        .map(|path| {
            let path = path.clone();
            std::thread::spawn(move || {
                let spec = load(&path);
                assert!(
                    spec.get("paths").is_some(),
                    "{} should be a valid OpenAPI spec",
                    path.display()
                );
                println!("Loaded spec: {}", path.display());
                extract_routes(&spec)
            })
        })
        .collect();

    handles
        .into_iter()
        .flat_map(|h| h.join().expect("spec loader thread should not panic"))
        .collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn load_reads_yaml_from_file() {
        let val = load(Path::new("specs_assets/taskflow.openapi.yml"));
        assert!(val.get("paths").is_some());
    }

    #[test]
    #[should_panic(expected = "should be readable")]
    fn load_panics_on_unreadable_file() {
        if std::env::var("USER").as_deref() == Ok("root") {
            return;
        }
        use std::os::unix::fs::PermissionsExt;
        let path = std::env::temp_dir().join("hermit_test_unreadable.txt");
        let _ = std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o644));
        std::fs::write(&path, "").unwrap();
        std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o000)).unwrap();
        load(&path);
    }

    #[test]
    #[should_panic(expected = "should contain valid YAML or JSON")]
    fn load_panics_on_invalid_yaml() {
        let path = std::env::temp_dir().join("hermit_test_invalid_yaml.txt");
        std::fs::write(&path, "key: {unclosed").unwrap();
        load(&path);
    }

    #[test]
    fn load_dir_returns_routes_from_all_files_in_directory() {
        let routes = load_dir(Path::new("specs_assets"));
        assert!(
            routes.len() >= 2,
            "expected routes from multiple spec files"
        );
    }

    #[test]
    #[should_panic]
    fn load_dir_panics_when_directory_is_empty() {
        let dir = std::env::temp_dir().join("hermit_test_empty_dir_for_load_dir");
        std::fs::create_dir_all(&dir).unwrap();
        std::fs::write(dir.join("dummy"), "").unwrap();
        for entry in std::fs::read_dir(&dir).unwrap() {
            std::fs::remove_file(entry.unwrap().path()).ok();
        }
        load_dir(&dir);
    }

    #[test]
    #[should_panic]
    fn load_dir_panics_when_a_file_is_not_a_valid_openapi_spec() {
        let dir = std::env::temp_dir().join("hermit_test_invalid_spec_dir_for_load_dir");
        std::fs::create_dir_all(&dir).unwrap();
        std::fs::write(dir.join("not_openapi.yaml"), "foo: bar").unwrap();
        load_dir(&dir);
    }
}
