use std::collections::BTreeSet;

use serde::Serialize;
use serde_json::Value;

#[derive(Debug, Clone, Serialize)]
pub(crate) struct RuntimeClassicProfileDifferenceValue {
    pub(crate) path: String,
    pub(crate) value: Value,
}

#[derive(Debug, Clone, Serialize)]
pub(crate) struct RuntimeClassicProfileDifference {
    pub(crate) field_path: String,
    pub(crate) values: Vec<RuntimeClassicProfileDifferenceValue>,
}

pub(crate) fn collect_json_multi_differences(
    path: &str,
    labeled_values: &[(String, Value)],
    differences: &mut Vec<RuntimeClassicProfileDifference>,
) {
    if labeled_values.is_empty() {
        return;
    }

    if labeled_values
        .iter()
        .all(|(_, value)| matches!(value, Value::Object(_)))
    {
        let mut keys = BTreeSet::new();
        for (_, value) in labeled_values {
            if let Value::Object(map) = value {
                keys.extend(map.keys().cloned());
            }
        }

        for key in keys {
            let next_path = format!("{path}.{key}");
            let nested = labeled_values
                .iter()
                .map(|(label, value)| {
                    let nested_value = match value {
                        Value::Object(map) => map.get(&key).cloned().unwrap_or(Value::Null),
                        _ => Value::Null,
                    };
                    (label.clone(), nested_value)
                })
                .collect::<Vec<_>>();
            collect_json_multi_differences(&next_path, &nested, differences);
        }
        return;
    }

    if labeled_values
        .iter()
        .all(|(_, value)| matches!(value, Value::Array(_)))
    {
        let max_len = labeled_values
            .iter()
            .filter_map(|(_, value)| match value {
                Value::Array(items) => Some(items.len()),
                _ => None,
            })
            .max()
            .unwrap_or(0);

        for index in 0..max_len {
            let next_path = format!("{path}[{index}]");
            let nested = labeled_values
                .iter()
                .map(|(label, value)| {
                    let nested_value = match value {
                        Value::Array(items) => items.get(index).cloned().unwrap_or(Value::Null),
                        _ => Value::Null,
                    };
                    (label.clone(), nested_value)
                })
                .collect::<Vec<_>>();
            collect_json_multi_differences(&next_path, &nested, differences);
        }
        return;
    }

    let first = &labeled_values[0].1;
    if labeled_values
        .iter()
        .skip(1)
        .all(|(_, value)| value == first)
    {
        return;
    }

    differences.push(RuntimeClassicProfileDifference {
        field_path: path.to_string(),
        values: labeled_values
            .iter()
            .map(|(label, value)| RuntimeClassicProfileDifferenceValue {
                path: label.clone(),
                value: value.clone(),
            })
            .collect(),
    });
}
