use std::sync::Arc;

use assert_matches::assert_matches;

use crate::json_schema;

use super::Element;

#[test]
fn should_parse_object_empty_schema() {
    let schema = json_schema!({});
    let fields = assert_matches!(schema, Element::Object(fields) => fields);
    assert!(fields.is_empty());
}

#[test]
fn should_parse_object_single_field_schema() {
    let schema = json_schema!({ "field_a" });
    let fields = assert_matches!(schema, Element::Object(fields) => fields);
    let fields = Arc::into_inner(fields)
        .unwrap()
        .into_iter()
        .collect::<Vec<_>>();
    let [field_a] = fields.try_into().unwrap();
    assert_matches!(field_a, ("field_a", None));
}

#[test]
fn should_parse_object_double_field_schema() {
    let schema = json_schema!({ "field_a", "field_b" });

    let fields = assert_matches!(schema, Element::Object(fields) => fields);
    let fields = Arc::into_inner(fields)
        .unwrap()
        .into_iter()
        .collect::<Vec<_>>();
    let [field_a, field_b] = fields.try_into().unwrap();
    assert_matches!(field_a, ("field_a", None));
    assert_matches!(field_b, ("field_b", None));
}

#[test]
fn should_parse_object_nested_one_level() {
    let schema = json_schema!({
        "field_a": {
            "field_b"
        }
    });

    let fields = assert_matches!(schema, Element::Object(fields) => fields);
    let fields = Arc::into_inner(fields)
        .unwrap()
        .into_iter()
        .collect::<Vec<_>>();
    let [field_a] = fields.try_into().unwrap();

    let elem = assert_matches!(field_a, ("field_a", Some(elem)) => elem);

    {
        let elem = Arc::into_inner(elem).unwrap();
        let fields = assert_matches!(elem, Element::Object(fields) => fields);
        let fields = Arc::into_inner(fields)
            .unwrap()
            .into_iter()
            .collect::<Vec<_>>();
        let [field_b] = fields.try_into().unwrap();
        assert_matches!(field_b, ("field_b", None));
    }
}

#[test]
fn should_parse_object_nested_two_levels() {
    let schema = json_schema!({
        "field_a": {
            "field_b": {
                "field_c"
            }
        }
    });

    let fields = assert_matches!(schema, Element::Object(fields) => fields);
    let fields = Arc::into_inner(fields)
        .unwrap()
        .into_iter()
        .collect::<Vec<_>>();
    let [field_a] = fields.try_into().unwrap();

    let elem = assert_matches!(field_a, ("field_a", Some(elem)) => elem);

    {
        let elem = Arc::into_inner(elem).unwrap();
        let fields = assert_matches!(elem, Element::Object(fields) => fields);
        let fields = Arc::into_inner(fields)
            .unwrap()
            .into_iter()
            .collect::<Vec<_>>();
        let [field_b] = fields.try_into().unwrap();
        let elem = assert_matches!(field_b, ("field_b", Some(elem)) => elem);

        {
            let elem = Arc::into_inner(elem).unwrap();
            let fields = assert_matches!(elem, Element::Object(fields) => fields);
            let fields = Arc::into_inner(fields)
                .unwrap()
                .into_iter()
                .collect::<Vec<_>>();
            let [field_c] = fields.try_into().unwrap();
            assert_matches!(field_c, ("field_c", None));
        }
    }
}

#[test]
fn should_parse_object_nested_with_leading_field() {
    let schema = json_schema!({
        "field_a",
        "field_b": {
            "field_c"
        }
    });

    let fields = assert_matches!(schema, Element::Object(fields) => fields);
    let fields = Arc::into_inner(fields)
        .unwrap()
        .into_iter()
        .collect::<Vec<_>>();
    let [field_a, field_b] = fields.try_into().unwrap();
    assert_matches!(field_a, ("field_a", None));

    {
        let elem = assert_matches!(field_b, ("field_b", Some(elem)) => elem);
        let elem = Arc::into_inner(elem).unwrap();
        let fields = assert_matches!(elem, Element::Object(fields) => fields);
        let fields = Arc::into_inner(fields)
            .unwrap()
            .into_iter()
            .collect::<Vec<_>>();
        let [field_c] = fields.try_into().unwrap();
        assert_matches!(field_c, ("field_c", None));
    }
}

#[test]
fn should_parse_object_nested_with_trailing_field() {
    let schema = json_schema!({
        "field_a": {
            "field_b"
        },
        "field_c"
    });

    let fields = assert_matches!(schema, Element::Object(fields) => fields);
    let fields = Arc::into_inner(fields)
        .unwrap()
        .into_iter()
        .collect::<Vec<_>>();
    let [field_a, field_c] = fields.try_into().unwrap();
    let elem = assert_matches!(field_a, ("field_a", Some(elem)) => elem);
    assert_matches!(field_c, ("field_c", None));

    {
        let elem = Arc::into_inner(elem).unwrap();
        let fields = assert_matches!(elem, Element::Object(fields) => fields);
        let fields = Arc::into_inner(fields)
            .unwrap()
            .into_iter()
            .collect::<Vec<_>>();
        let [field_b] = fields.try_into().unwrap();
        assert_matches!(field_b, ("field_b", None));
    }
}

#[test]
fn should_parse_array() {
    let schema = json_schema!({
        "field_a": [{
            "field_b",
            "field_c"
        }]
    });

    let fields = assert_matches!(schema, Element::Object(fields) => fields);
    let fields = Arc::into_inner(fields)
        .unwrap()
        .into_iter()
        .collect::<Vec<_>>();
    let [field_a] = fields.try_into().unwrap();
    let elem = assert_matches!(field_a, ("field_a", Some(elem)) => elem);
    let elem = Arc::into_inner(elem).unwrap();
    let elem = assert_matches!(elem, Element::Array(elem) => elem);
    let elem = Arc::into_inner(elem).unwrap();

    {
        let fields = assert_matches!(elem, Element::Object(fields) => fields);
        let fields = Arc::into_inner(fields)
            .unwrap()
            .into_iter()
            .collect::<Vec<_>>();
        let [field_b, field_c] = fields.try_into().unwrap();
        assert_matches!(field_b, ("field_b", None));
        assert_matches!(field_c, ("field_c", None));
    }
}
