use std::collections::{HashMap, HashSet};

use crate::chromium::{InstalledBrowserRetriever, PLATFORM_SUPPORTED_BROWSERS};

/// Mechanisms that load data into the importer
pub struct NativeImporterMetadata {
    /// Identifies the importer
    pub id: String,
    /// Describes the strategies used to obtain imported data
    pub loaders: Vec<String>,
    /// Identifies the instructions for the importer
    pub instructions: String,
}

/// Returns a map of supported importers based on the current platform.
///
/// Only browsers listed in PLATFORM_SUPPORTED_BROWSERS will have the "chromium" loader.
/// All importers will have the "file" loader.
pub fn get_supported_importers<T: InstalledBrowserRetriever>(
    mas_build: bool,
) -> HashMap<String, NativeImporterMetadata> {
    let mut map = HashMap::new();

    // Check for installed browsers
    let installed_browsers = T::get_installed_browsers(mas_build);

    const IMPORTERS: &[(&str, &str)] = &[
        ("arccsv", "Arc"),
        ("bravecsv", "Brave"),
        ("chromecsv", "Chrome"),
        ("chromiumcsv", "Chromium"),
        ("edgecsv", "Microsoft Edge"),
        ("operacsv", "Opera"),
        ("vivaldicsv", "Vivaldi"),
    ];

    let supported: HashSet<&'static str> =
        PLATFORM_SUPPORTED_BROWSERS.iter().map(|b| b.name).collect();

    for (id, browser_name) in IMPORTERS {
        let mut loaders: Vec<String> = vec!["file".to_string()];
        if supported.contains(browser_name) {
            loaders.push("chromium".to_string());
        }

        if installed_browsers.contains(&browser_name.to_string()) {
            map.insert(
                id.to_string(),
                NativeImporterMetadata {
                    id: id.to_string(),
                    loaders,
                    instructions: "chromium".to_string(),
                },
            );
        }
    }

    map
}

// Tests are cfg-gated based upon OS, and must be compiled/run on each OS for full coverage
#[cfg(test)]
mod tests {
    use std::collections::HashSet;

    use super::*;
    use crate::chromium::{InstalledBrowserRetriever, SUPPORTED_BROWSER_MAP};

    pub struct MockInstalledBrowserRetriever {}

    impl InstalledBrowserRetriever for MockInstalledBrowserRetriever {
        fn get_installed_browsers(_mas_build: bool) -> Vec<String> {
            SUPPORTED_BROWSER_MAP
                .keys()
                .map(|browser| browser.to_string())
                .collect()
        }
    }

    fn map_keys(map: &HashMap<String, NativeImporterMetadata>) -> HashSet<String> {
        map.keys().cloned().collect()
    }

    fn get_loaders(map: &HashMap<String, NativeImporterMetadata>, id: &str) -> HashSet<String> {
        map.get(id)
            .map(|m| m.loaders.iter().cloned().collect::<HashSet<_>>())
            .unwrap_or_default()
    }

    #[cfg(target_os = "macos")]
    #[test]
    fn macos_returns_all_known_importers() {
        let map = get_supported_importers::<MockInstalledBrowserRetriever>(false);

        let expected: HashSet<String> = HashSet::from([
            "arccsv".to_string(),
            "chromecsv".to_string(),
            "chromiumcsv".to_string(),
            "bravecsv".to_string(),
            "operacsv".to_string(),
            "vivaldicsv".to_string(),
            "edgecsv".to_string(),
        ]);
        assert_eq!(map.len(), expected.len());
        assert_eq!(map_keys(&map), expected);

        for (key, meta) in map.iter() {
            assert_eq!(&meta.id, key);
            assert_eq!(meta.instructions, "chromium");
            assert!(meta.loaders.contains(&"file".to_owned()));
        }
    }

    #[cfg(target_os = "macos")]
    #[test]
    fn macos_specific_loaders_match_const_array() {
        let map = get_supported_importers::<MockInstalledBrowserRetriever>(false);
        let ids = [
            "arccsv",
            "chromecsv",
            "chromiumcsv",
            "bravecsv",
            "operacsv",
            "vivaldicsv",
            "edgecsv",
        ];
        for id in ids {
            let loaders = get_loaders(&map, id);
            assert!(loaders.contains("file"));
            assert!(loaders.contains("chromium"), "missing chromium for {id}");
        }
    }

    #[cfg(target_os = "linux")]
    #[test]
    fn returns_all_known_importers() {
        let map = get_supported_importers::<MockInstalledBrowserRetriever>(false);

        let expected: HashSet<String> = HashSet::from([
            "chromecsv".to_string(),
            "chromiumcsv".to_string(),
            "bravecsv".to_string(),
            "operacsv".to_string(),
        ]);
        assert_eq!(map.len(), expected.len());
        assert_eq!(map_keys(&map), expected);

        for (key, meta) in map.iter() {
            assert_eq!(&meta.id, key);
            assert_eq!(meta.instructions, "chromium");
            assert!(meta.loaders.contains(&"file".to_owned()));
        }
    }

    #[cfg(target_os = "linux")]
    #[test]
    fn linux_specific_loaders_match_const_array() {
        let map = get_supported_importers::<MockInstalledBrowserRetriever>(false);
        let ids = ["chromecsv", "chromiumcsv", "bravecsv", "operacsv"];

        for id in ids {
            let loaders = get_loaders(&map, id);
            assert!(loaders.contains("file"));
            assert!(loaders.contains("chromium"), "missing chromium for {id}");
        }
    }

    #[cfg(target_os = "windows")]
    #[test]
    fn returns_all_known_importers() {
        let map = get_supported_importers::<MockInstalledBrowserRetriever>(false);

        let expected: HashSet<String> = HashSet::from([
            "bravecsv".to_string(),
            "chromecsv".to_string(),
            "chromiumcsv".to_string(),
            "edgecsv".to_string(),
            "operacsv".to_string(),
            "vivaldicsv".to_string(),
        ]);
        assert_eq!(map.len(), expected.len());
        assert_eq!(map_keys(&map), expected);

        for (key, meta) in map.iter() {
            assert_eq!(&meta.id, key);
            assert_eq!(meta.instructions, "chromium");
            assert!(meta.loaders.contains(&"file".to_owned()));
        }
    }

    #[cfg(target_os = "windows")]
    #[test]
    fn windows_specific_loaders_match_const_array() {
        let map = get_supported_importers::<MockInstalledBrowserRetriever>(false);
        let ids = [
            "bravecsv",
            "chromecsv",
            "chromiumcsv",
            "edgecsv",
            "operacsv",
            "vivaldicsv",
        ];

        for id in ids {
            let loaders = get_loaders(&map, id);
            assert!(loaders.contains("file"));
            assert!(loaders.contains("chromium"), "missing chromium for {id}");
        }
    }
}
