use async_recursion::async_recursion;
use regex::Regex;
use std::error::Error;
use std::path::Path;
use tokio::fs;

pub async fn count_tests_in_tests_folder() -> Result<u64, Box<dyn Error + Send + Sync>> {
    let tests_dir = Path::new("./tests");

    #[async_recursion]
    async fn inner(dir: &Path, re: &Regex) -> Result<u64, Box<dyn Error + Send + Sync>> {
        let mut total: u64 = 0;

        let mut entries = fs::read_dir(dir).await.map_err(|e| {
            Box::<dyn Error + Send + Sync>::from(format!("read_dir {}: {}", dir.display(), e))
        })?;

        while let Some(entry) = entries.next_entry().await? {
            let path = entry.path();

            if path.is_dir() {
                total = total.checked_add(inner(&path, re).await?).ok_or_else(|| {
                    Box::<dyn Error + Send + Sync>::from("overflow counting tests")
                })?;
            } else if path.extension().and_then(|e| e.to_str()) == Some("rs") {
                let content = fs::read_to_string(&path).await.map_err(|e| {
                    Box::<dyn Error + Send + Sync>::from(format!(
                        "failed to read {}: {}",
                        path.display(),
                        e
                    ))
                })?;
                total += re.find_iter(&content).count() as u64;
            }
        }

        Ok(total)
    }

    if !tests_dir.exists() {
        return Ok(0);
    }

    let test_macro_re = Regex::new(r"#\[\s*(?:\w+:)?test\s*\]").unwrap();
    inner(tests_dir, &test_macro_re).await
}