use std::path::{Path, PathBuf};

/// Scans a repository for Rust test code (`#[test]` or `#[cfg(test)]`) placed
/// outside of crate-level `tests/` directories.
///
/// A `tests/` directory is considered valid only when its parent contains a
/// `Cargo.toml` (i.e., it is directly owned by a crate). The workspace-root
/// `tests/` directory is therefore treated as an invalid location.
///
/// Returns the paths of all offending `.rs` files.
pub fn scan_for_misplaced_tests(repo_dir: &Path) -> Vec<PathBuf> {
    let mut violations = Vec::new();
    scan_dir(repo_dir, &mut violations);
    violations
}

fn scan_dir(dir: &Path, violations: &mut Vec<PathBuf>) {
    let entries = match std::fs::read_dir(dir) {
        Ok(e) => e,
        Err(_) => return,
    };

    for entry in entries.flatten() {
        let path = entry.path();
        let name = entry.file_name();
        let name = name.to_string_lossy();

        if path.is_dir() {
            if name.starts_with('.') || name == "target" {
                continue;
            }
            // Skip tests/ only when it belongs to a crate (parent directory has a Cargo.toml).
            // Workspace-root tests/ does not have a sibling Cargo.toml, so it is scanned.
            if name == "tests" && dir.join("Cargo.toml").exists() {
                continue;
            }
            scan_dir(&path, violations);
        } else if name.ends_with(".rs") && file_contains_test_markers(&path) {
            violations.push(path);
        }
    }
}

fn file_contains_test_markers(path: &Path) -> bool {
    match std::fs::read_to_string(path) {
        Ok(content) => content.contains("#[test]") || content.contains("#[cfg(test)]"),
        Err(_) => false,
    }
}
