use recordpath::{enumerate, is_vector, match_path, parse, PathType};
use serde::Deserialize;
use serde_json::Value;
use std::collections::HashSet;

const MATCH_JSON: &str = include_str!("../../interop-tests/match.json");
const ENUMERATE_JSON: &str = include_str!("../../interop-tests/enumerate.json");
const PARSE_JSON: &str = include_str!("../../interop-tests/parse.json");

// -- Match tests --

#[derive(Deserialize)]
struct MatchFixture {
    tests: Vec<MatchTest>,
}

#[derive(Deserialize)]
struct MatchTest {
    description: String,
    record: Value,
    path: String,
    expected: Vec<Value>,
}

#[test]
fn match_tests() {
    let f: MatchFixture = serde_json::from_str(MATCH_JSON).unwrap();
    for t in &f.tests {
        let result = match_path(&t.record, &t.path);
        assert_eq!(result, t.expected, "FAIL: {}", t.description);
    }
}

// -- Enumerate tests --

#[derive(Deserialize)]
struct EnumFixture {
    tests: Vec<EnumTest>,
}

#[derive(Deserialize)]
struct EnumTest {
    description: String,
    record: Value,
    expected: Vec<EnumExpected>,
}

#[derive(Deserialize)]
struct EnumExpected {
    path: String,
    r#type: String,
}

#[test]
fn enumerate_tests() {
    let f: EnumFixture = serde_json::from_str(ENUMERATE_JSON).unwrap();
    for t in &f.tests {
        let result_set: HashSet<String> = enumerate(&t.record)
            .map(|(p, _value)| {
                let ty = match p.path_type {
                    PathType::Scalar => "scalar",
                    PathType::Vector => "vector",
                };
                format!("{}:{ty}", p.path)
            })
            .collect();
        let expected_set: HashSet<String> = t
            .expected
            .iter()
            .map(|p| format!("{}:{}", p.path, p.r#type))
            .collect();
        assert_eq!(result_set, expected_set, "FAIL: {}", t.description);
    }
}

// -- Parse type classification tests --

#[derive(Deserialize)]
struct ParseFixture {
    type_tests: Vec<TypeTest>,
    invalid_tests: Vec<InvalidTest>,
}

#[derive(Deserialize)]
struct TypeTest {
    description: String,
    path: String,
    r#type: String,
}

#[derive(Deserialize)]
struct InvalidTest {
    description: String,
    path: String,
}

#[test]
fn type_classification_tests() {
    let f: ParseFixture = serde_json::from_str(PARSE_JSON).unwrap();
    for t in &f.type_tests {
        let result = if is_vector(&t.path) {
            "vector"
        } else {
            "scalar"
        };
        assert_eq!(result, t.r#type, "FAIL: {}", t.description);
    }
}

#[test]
fn invalid_parse_tests() {
    let f: ParseFixture = serde_json::from_str(PARSE_JSON).unwrap();
    for t in &f.invalid_tests {
        assert!(
            parse(&t.path).is_err(),
            "FAIL: {} — expected parse('{}') to fail",
            t.description,
            t.path
        );
    }
}
