use std::collections::HashSet;

use serde_json::Value;

/// Handles required set.
fn required_set(schema: &Value) -> HashSet<String> {
    schema
        .get("required")
        .and_then(Value::as_array)
        .cloned()
        .unwrap_or_default()
        .into_iter()
        .filter_map(|v| v.as_str().map(ToString::to_string))
        .collect()
}

/// Handles profile fields.
fn profile_fields(profile: &Value, key: &str) -> Vec<String> {
    profile
        .get(key)
        .and_then(Value::as_array)
        .cloned()
        .unwrap_or_default()
        .into_iter()
        .filter_map(|v| v.as_str().map(ToString::to_string))
        .collect()
}

fn bootstrap_required_fields(schema: &Value) -> HashSet<String> {
    schema
        .get("allOf")
        .and_then(Value::as_array)
        .into_iter()
        .flatten()
        .find_map(|entry| {
            let mode = entry
                .get("if")
                .and_then(|v| v.get("properties"))
                .and_then(|v| v.get("provenance_start_mode"))
                .and_then(|v| v.get("const"))
                .and_then(Value::as_str);
            (mode == Some("dataset_bootstrap"))
                .then(|| required_set(entry.get("then").unwrap_or(&Value::Null)))
        })
        .unwrap_or_default()
}

/// Tests that conformance profile required fields exist in schemas.
#[test]
fn conformance_profile_required_fields_exist_in_schemas() {
    let profile: Value = serde_json::from_str(include_str!("../schemas/conformance_profiles.json"))
        .expect("conformance profile json");
    let transform_schema: Value =
        serde_json::from_str(include_str!("../schemas/transform.schema.json"))
            .expect("transform schema");
    let publish_schema: Value =
        serde_json::from_str(include_str!("../schemas/publish.schema.json"))
            .expect("publish schema");

    let transform_required = required_set(&transform_schema);
    let publish_required = required_set(&publish_schema);
    let transform_bootstrap_required = bootstrap_required_fields(&transform_schema);
    let publish_bootstrap_required = bootstrap_required_fields(&publish_schema);

    for mode in ["source_capture", "transport_capture"] {
        let capture_profile = profile
            .get("profiles")
            .and_then(|v| v.get(mode))
            .unwrap_or_else(|| panic!("{mode} profile"));
        for field in profile_fields(capture_profile, "required_transform_fields") {
            assert!(
                transform_required.contains(&field),
                "missing required transform field in schema: {field}"
            );
        }
        for field in profile_fields(capture_profile, "required_publish_fields") {
            assert!(
                publish_required.contains(&field),
                "missing required publish field in schema: {field}"
            );
        }
    }

    let bootstrap = profile
        .get("profiles")
        .and_then(|v| v.get("dataset_bootstrap"))
        .expect("dataset_bootstrap profile");
    for field in profile_fields(bootstrap, "required_transform_fields") {
        assert!(
            transform_bootstrap_required.contains(&field),
            "missing bootstrap transform conditional requirement in schema: {field}"
        );
    }
    for field in profile_fields(bootstrap, "required_publish_fields") {
        assert!(
            publish_bootstrap_required.contains(&field),
            "missing bootstrap publish conditional requirement in schema: {field}"
        );
    }
}
