use std::env;
use std::fs::File;
use std::path::PathBuf;

use std::io::{BufRead, BufReader};
use std::sync::OnceLock;

#[derive(Debug, Clone)]
struct IgnorePattern {
    os: Option<String>,
    arch: Option<String>,
    target_env: Option<String>,
    engine: Option<String>,
    compiler: Option<String>,
    pattern_to_ignore: String,
}

impl IgnorePattern {
    fn should_ignore(
        &self,
        os: &str,
        arch: &str,
        target_env: &str,
        engine: &str,
        compiler: &str,
        canonical_path: &str,
    ) -> bool {
        self.os.as_ref().is_none_or(|val| val == os)
            && self.arch.as_ref().is_none_or(|val| val == arch)
            && self.target_env.as_ref().is_none_or(|val| val == target_env)
            && self.engine.as_ref().is_none_or(|val| val == engine)
            && self.compiler.as_ref().is_none_or(|val| val == compiler)
            && (self.pattern_to_ignore == "*" || canonical_path.contains(&*self.pattern_to_ignore))
    }
}

#[derive(Debug, Clone)]
pub struct Ignores {
    /// The canonical path, and the set of features
    patterns: Vec<IgnorePattern>,
}

impl Ignores {
    /// If the path matches any of the paths on the list
    pub fn should_ignore(
        &self,
        os: &str,
        arch: &str,
        target_env: &str,
        engine: &str,
        compiler: &str,
        canonical_path: &str,
    ) -> bool {
        self.patterns.iter().any(|p| {
            // println!(" -> {:?}", p);
            p.should_ignore(os, arch, target_env, engine, compiler, canonical_path)
        })
    }

    pub fn should_ignore_host(&self, engine: &str, compiler: &str, canonical_path: &str) -> bool {
        static CFG_TARGET_OS: OnceLock<String> = OnceLock::new();
        let target_os = CFG_TARGET_OS.get_or_init(|| {
            env::var("CFG_TARGET_OS")
                .expect("CFG_TARGET_OS variable expected from build.rs")
                .to_string()
        });
        static CFG_TARGET_ARCH: OnceLock<String> = OnceLock::new();
        let target_arch = CFG_TARGET_ARCH.get_or_init(|| {
            env::var("CFG_TARGET_ARCH")
                .expect("CFG_TARGET_ARCH variable expected from build.rs")
                .to_string()
        });
        static CFG_TARGET_ENV: OnceLock<String> = OnceLock::new();
        let target_env = CFG_TARGET_ENV.get_or_init(|| {
            env::var("CFG_TARGET_ENV")
                .expect("CFG_TARGET_ENV variable expected from build.rs")
                .to_string()
        });

        self.should_ignore(
            target_os,
            target_arch,
            target_env,
            engine,
            compiler,
            canonical_path,
        )
    }

    /// Build a Ignore structure from a file path
    pub fn build_from_path(path: PathBuf) -> Ignores {
        let file = File::open(path).unwrap();
        let reader = BufReader::new(file);
        let mut patterns = Vec::new();

        for (i, line) in reader.lines().enumerate() {
            let line = line.unwrap();
            // If the line has a `#` we discard all the content that comes after
            let line = line
                .split_once("#")
                .map(|(line, _comment)| line)
                .unwrap_or_else(|| &line)
                .trim()
                .to_string();

            // If the lines contains ` ` it means the test should be ignored
            // on the features exposed
            if let Some((platform_specifier, pattern_to_ignore)) = line.split_once(" ") {
                let mut os: Option<String> = None;
                let mut arch: Option<String> = None;
                let mut target_env: Option<String> = None;
                let mut engine: Option<String> = None;
                let mut compiler: Option<String> = None;
                for alias in platform_specifier.trim().split('+') {
                    match alias {
                        // Operating Systems
                        "windows" | "macos" | "linux" => {
                            os = Some(alias.to_string());
                        }
                        // Environments
                        "musl" => {
                            target_env = Some(alias.to_string());
                        }
                        // Chipset architectures
                        "aarch64" | "x86" | "x64" | "riscv64" | "loongarch64" => {
                            arch = Some(alias.to_string());
                        }
                        // Engines
                        "universal" | "engine" => {
                            engine = Some(alias.to_string());
                        }
                        // Compilers
                        "cranelift" | "llvm" | "singlepass" => {
                            compiler = Some(alias.to_string());
                        }
                        other => {
                            panic!(
                                "Alias {:?} not currently supported (defined in ignores.txt in line {})",
                                other,
                                i + 1
                            );
                        }
                    }
                }
                let pattern_to_ignore = pattern_to_ignore.trim().to_string();
                patterns.push(IgnorePattern {
                    os,
                    arch,
                    target_env,
                    engine,
                    compiler,
                    pattern_to_ignore,
                });
            } else {
                if line.is_empty() {
                    continue;
                }
                patterns.push(IgnorePattern {
                    os: None,
                    arch: None,
                    target_env: None,
                    engine: None,
                    compiler: None,
                    pattern_to_ignore: line,
                });
            };
        }
        Ignores { patterns }
    }
}

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

    #[test]
    fn features_match() -> Result<(), ()> {
        assert!(
            IgnorePattern {
                os: None,
                arch: None,
                target_env: None,
                engine: None,
                compiler: None,
                pattern_to_ignore: "*".to_string()
            }
            .should_ignore(
                "unknown",
                "unknown",
                "",
                "engine",
                "compiler",
                "some::random::text"
            )
        );
        assert!(
            IgnorePattern {
                os: None,
                arch: None,
                target_env: None,
                engine: None,
                compiler: None,
                pattern_to_ignore: "some::random".to_string()
            }
            .should_ignore(
                "unknown",
                "unknown",
                "",
                "engine",
                "compiler",
                "some::random::text"
            )
        );
        assert!(
            !IgnorePattern {
                os: Some("macos".to_string()),
                arch: None,
                target_env: None,
                engine: None,
                compiler: None,
                pattern_to_ignore: "other".to_string()
            }
            .should_ignore(
                "unknown",
                "unknown",
                "",
                "engine",
                "compiler",
                "some::random::text"
            )
        );
        assert!(
            !IgnorePattern {
                os: Some("macos".to_string()),
                arch: None,
                target_env: None,
                engine: Some("universal".to_string()),
                compiler: None,
                pattern_to_ignore: "other".to_string()
            }
            .should_ignore(
                "macos",
                "unknown",
                "",
                "universal",
                "compiler",
                "some::random::text"
            )
        );
        Ok(())
    }
}
