use std::collections::HashMap;

use bstr::{BStr, BString, ByteSlice};
use gix_attributes::State;
use gix_pathspec::{MagicSignature, Pattern, SearchMode};
use std::sync::LazyLock;

#[test]
fn baseline() {
    for (pattern, exit_code) in BASELINE.iter() {
        let res = gix_pathspec::parse(pattern, Default::default());
        assert_eq!(
            res.is_ok(),
            *exit_code == 0,
            "{pattern:?} disagrees with baseline: {res:?}"
        );
        if let Ok(pat) = res {
            let actual = pat.to_bstring();
            assert_eq!(
                pat,
                gix_pathspec::parse(actual.as_ref(), Default::default()).expect("still valid"),
                "{pattern} != {actual}: display must roundtrip into actual pattern"
            );
        }
        let p = gix_pathspec::Pattern::from_literal(pattern, Default::default());
        assert!(matches!(p.search_mode, SearchMode::Literal));
    }
}

mod invalid;
mod valid;

/// A way to specify expectations more easily by simplifying assignments.
#[derive(Debug, Clone, PartialEq, Eq)]
struct NormalizedPattern {
    path: BString,
    signature: MagicSignature,
    search_mode: SearchMode,
    attributes: Vec<(BString, State)>,
}

impl From<Pattern> for NormalizedPattern {
    fn from(p: Pattern) -> Self {
        NormalizedPattern {
            path: p.path().to_owned(),
            signature: p.signature,
            search_mode: p.search_mode,
            attributes: p
                .attributes
                .into_iter()
                .map(|attr| (attr.name.as_str().into(), attr.state))
                .collect(),
        }
    }
}

static BASELINE: LazyLock<HashMap<BString, usize>> = LazyLock::new(|| {
    let base = gix_testtools::scripted_fixture_read_only("parse_baseline.sh").unwrap();

    (|| -> crate::Result<_> {
        let mut map = HashMap::new();
        let baseline = std::fs::read(base.join("baseline.git"))?;
        let mut lines = baseline.lines();
        while let Some(spec) = lines.next() {
            let exit_code = lines.next().expect("two lines per baseline").to_str()?.parse()?;
            map.insert(spec.into(), exit_code);
        }
        Ok(map)
    })()
    .unwrap()
});

fn check_valid_inputs<'a>(inputs: impl IntoIterator<Item = (&'a str, NormalizedPattern)>) {
    for (input, expected) in inputs.into_iter() {
        assert!(
            check_against_baseline(input),
            "This pathspec is invalid in git: {input}"
        );

        let pattern = gix_pathspec::parse(input.as_bytes(), Default::default())
            .unwrap_or_else(|_| panic!("parsing should not fail with pathspec {input}"));
        let pattern: NormalizedPattern = pattern.into();
        assert_eq!(pattern, expected, "while checking input: \"{input}\"");
    }
}

fn check_against_baseline(pathspec: &str) -> bool {
    let key: &BStr = pathspec.into();
    let base = BASELINE
        .get(key)
        .unwrap_or_else(|| panic!("missing baseline for pathspec: {pathspec:?}"));
    *base == 0
}
