use crate::parse::PackageData;
use std::collections::HashMap;

/// Recursively resolves all transitive dependencies for a package.
///
/// Filters out packages not present in the repository index. The returned
/// list is reversed so dependencies appear before the packages that need them,
/// and consecutive duplicates are removed with `dedup`.
pub fn resolve_requires(package: &str, data: &HashMap<String, PackageData>) -> Vec<String> {
    let Some(pkg_data) = data.get(package) else {
        return Vec::new();
    };

    let mut requires: Vec<String> = pkg_data
        .requires
        .split_whitespace()
        .filter(|s| data.contains_key(*s))
        .map(ToString::to_string)
        .collect();

    let mut i = 0;
    while i < requires.len() {
        let req = &requires[i];

        if let Some(pkg_data) = data.get(req) {
            let sub_requires: Vec<String> = pkg_data
                .requires
                .split_whitespace()
                .map(ToString::to_string)
                .collect();

            for sub in sub_requires {
                if !requires.contains(&sub) && data.contains_key(&sub) {
                    requires.push(sub);
                }
            }
        }
        i += 1;
    }

    requires.reverse();
    requires.dedup();

    requires
}

/// Unit tests for dependency resolution logic.
#[cfg(test)]
mod tests {
    use super::*;
    use crate::parse::PackageData;

    fn make_pkg(name: &str, requires: &str) -> (String, PackageData) {
        (
            name.to_string(),
            PackageData {
                prgnam: name.to_string(),
                version: "1.0".to_string(),
                requires: requires.to_string(),
                homepage: String::new(),
                download: String::new(),
                md5sum: String::new(),
                download_x86_64: String::new(),
                md5sum_x86_64: String::new(),
                maintainer: String::new(),
                email: String::new(),
                category: String::new(),
                build: String::new(),
                tag: String::new(),
                pkgtype: String::new(),
                arch: String::new(),
                package: String::new(),
            },
        )
    }

    /// Package with no dependencies returns empty list.
    #[test]
    fn test_no_deps() {
        let data: HashMap<String, PackageData> = [make_pkg("foo", "")].into_iter().collect();
        assert!(resolve_requires("foo", &data).is_empty());
    }

    /// Package not in repo returns empty list.
    #[test]
    fn test_unknown_package() {
        let data: HashMap<String, PackageData> = HashMap::new();
        assert!(resolve_requires("unknown", &data).is_empty());
    }

    /// Direct dependencies are resolved correctly.
    #[test]
    fn test_direct_deps() {
        let data: HashMap<String, PackageData> = [
            make_pkg("app", "libA libB"),
            make_pkg("libA", ""),
            make_pkg("libB", ""),
        ]
        .into_iter()
        .collect();

        let deps = resolve_requires("app", &data);
        assert!(deps.contains(&"libA".to_string()));
        assert!(deps.contains(&"libB".to_string()));
    }

    /// Transitive dependencies are resolved recursively.
    #[test]
    fn test_transitive_deps() {
        let data: HashMap<String, PackageData> = [
            make_pkg("app", "libA"),
            make_pkg("libA", "libB"),
            make_pkg("libB", ""),
        ]
        .into_iter()
        .collect();

        let deps = resolve_requires("app", &data);
        assert!(deps.contains(&"libA".to_string()));
        assert!(deps.contains(&"libB".to_string()));
    }

    /// Dependencies not present in the repo are filtered out.
    #[test]
    fn test_missing_dep_filtered() {
        let data: HashMap<String, PackageData> =
            [make_pkg("app", "libA missing-pkg")].into_iter().collect();

        let deps = resolve_requires("app", &data);
        assert!(!deps.contains(&"missing-pkg".to_string()));
    }
}
