//! Convert regular JSON values to DynamoDB JSON format.
//!
//! # DynamoDB type mapping
//!
//! | JSON type | DynamoDB descriptor |
//! |-----------|---------------------|
//! | String    | `{"S": "…"}`       |
//! | Number    | `{"N": "…"}`       |
//! | Boolean   | `{"BOOL": …}`     |
//! | Null      | `{"NULL": true}`   |
//! | Array     | `{"L": […]}`      |
//! | Object    | `{"M": {…}}`      |

use rayon::prelude::*;
use serde_json::{Map, Value};

/// Marshall a single JSON [`Value`] into its DynamoDB JSON representation.
///
/// # Examples
///
/// ```
/// use serde_json::json;
/// # use dynojson::marshall::marshall_value;
///
/// let input = json!({"name": "Alice", "age": 30});
/// let output = marshall_value(&input);
/// // output ≈ {"M": {"name": {"S": "Alice"}, "age": {"N": "30"}}}
/// ```
pub fn marshall_value(value: &Value) -> Value {
    match value {
        Value::String(s) => wrap("S", Value::String(s.clone())),
        Value::Number(n) => wrap("N", Value::String(n.to_string())),
        Value::Bool(b) => wrap("BOOL", Value::Bool(*b)),
        Value::Null => wrap("NULL", Value::Bool(true)),
        Value::Array(arr) => {
            let items: Vec<Value> = arr.iter().map(marshall_value).collect();
            wrap("L", Value::Array(items))
        }
        Value::Object(obj) => {
            let mut map = Map::new();
            for (k, v) in obj {
                map.insert(k.clone(), marshall_value(v));
            }
            wrap("M", Value::Object(map))
        }
    }
}

/// Marshall a top-level JSON value.
///
/// If the input is an object, each top-level key is marshalled individually
/// (matching AWS SDK `marshall` behaviour for a DynamoDB Item — no outer `M`
/// wrapper).  
/// If the input is an array, each element is marshalled.
pub fn marshall_top_level(value: &Value) -> Value {
    match value {
        Value::Object(obj) => {
            let pairs: Vec<(String, Value)> = obj
                .into_iter()
                .collect::<Vec<_>>()
                .par_iter()
                .map(|(k, v)| (k.to_string(), marshall_value(v)))
                .collect();
            Value::Object(pairs.into_iter().collect())
        }
        Value::Array(arr) => {
            let items: Vec<Value> = arr.par_iter().map(marshall_top_level).collect();
            Value::Array(items)
        }
        other => marshall_value(other),
    }
}

/// Wrap a value in a single-key DynamoDB descriptor object.
fn wrap(key: &str, value: Value) -> Value {
    let mut map = Map::new();
    map.insert(key.to_owned(), value);
    Value::Object(map)
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    #[test]
    fn marshall_string() {
        assert_eq!(marshall_value(&json!("hello")), json!({"S": "hello"}));
    }

    #[test]
    fn marshall_number_integer() {
        assert_eq!(marshall_value(&json!(42)), json!({"N": "42"}));
    }

    #[test]
    fn marshall_number_float() {
        assert_eq!(marshall_value(&json!(3.14)), json!({"N": "3.14"}));
    }

    #[test]
    fn marshall_bool_true() {
        assert_eq!(marshall_value(&json!(true)), json!({"BOOL": true}));
    }

    #[test]
    fn marshall_bool_false() {
        assert_eq!(marshall_value(&json!(false)), json!({"BOOL": false}));
    }

    #[test]
    fn marshall_null() {
        assert_eq!(marshall_value(&json!(null)), json!({"NULL": true}));
    }

    #[test]
    fn marshall_array() {
        let input = json!(["a", "b", "c"]);
        let expected = json!({"L": [{"S": "a"}, {"S": "b"}, {"S": "c"}]});
        assert_eq!(marshall_value(&input), expected);
    }

    #[test]
    fn marshall_nested_object() {
        let input = json!({"key1": "value1", "key2": "value2"});
        let expected = json!({"M": {"key1": {"S": "value1"}, "key2": {"S": "value2"}}});
        assert_eq!(marshall_value(&input), expected);
    }

    #[test]
    fn marshall_top_level_object() {
        let input = json!({
            "object": {
                "array": ["a", "b", "c"],
                "subobject": {"key1": "value1", "key2": "value2"}
            },
            "number": 1,
            "string": "string",
            "boolean": true,
            "null": null
        });
        let expected = json!({
            "object": {"M": {
                "array": {"L": [{"S": "a"}, {"S": "b"}, {"S": "c"}]},
                "subobject": {"M": {"key1": {"S": "value1"}, "key2": {"S": "value2"}}}
            }},
            "number": {"N": "1"},
            "string": {"S": "string"},
            "boolean": {"BOOL": true},
            "null": {"NULL": true}
        });
        assert_eq!(marshall_top_level(&input), expected);
    }

    #[test]
    fn marshall_top_level_array() {
        let input = json!([
            {"object": {"key1": "value1", "key2": "value2"}},
            {"object": {"key3": "value3", "key4": "value4"}}
        ]);
        let expected = json!([
            {"object": {"M": {"key1": {"S": "value1"}, "key2": {"S": "value2"}}}},
            {"object": {"M": {"key3": {"S": "value3"}, "key4": {"S": "value4"}}}}
        ]);
        assert_eq!(marshall_top_level(&input), expected);
    }

    // ── Empty values ───────────────────────────────────────────────────

    #[test]
    fn marshall_empty_string() {
        assert_eq!(marshall_value(&json!("")), json!({"S": ""}));
    }

    #[test]
    fn marshall_empty_array() {
        assert_eq!(marshall_value(&json!([])), json!({"L": []}));
    }

    #[test]
    fn marshall_empty_object() {
        assert_eq!(marshall_value(&json!({})), json!({"M": {}}));
    }

    // ── Number edge cases ──────────────────────────────────────────────

    #[test]
    fn marshall_negative_number() {
        assert_eq!(marshall_value(&json!(-42)), json!({"N": "-42"}));
    }

    #[test]
    fn marshall_zero() {
        assert_eq!(marshall_value(&json!(0)), json!({"N": "0"}));
    }

    #[test]
    fn marshall_negative_float() {
        assert_eq!(marshall_value(&json!(-3.14)), json!({"N": "-3.14"}));
    }

    // ── Mixed / complex structures ─────────────────────────────────────

    #[test]
    fn marshall_mixed_type_array() {
        let input = json!(["hello", 42, true, null, {"key": "val"}, [1, 2]]);
        let expected = json!({"L": [
            {"S": "hello"},
            {"N": "42"},
            {"BOOL": true},
            {"NULL": true},
            {"M": {"key": {"S": "val"}}},
            {"L": [{"N": "1"}, {"N": "2"}]}
        ]});
        assert_eq!(marshall_value(&input), expected);
    }

    #[test]
    fn marshall_deeply_nested() {
        let input = json!({"level1": {"level2": [{"level3": "deep"}]}});
        let expected = json!({"M": {
            "level1": {"M": {
                "level2": {"L": [
                    {"M": {"level3": {"S": "deep"}}}
                ]}
            }}
        }});
        assert_eq!(marshall_value(&input), expected);
    }

    #[test]
    fn marshall_top_level_all_types() {
        // A single Item containing every marshallable JSON type.
        let input = json!({
            "str": "hello",
            "num": 42,
            "flt": 3.14,
            "yes": true,
            "no": false,
            "nul": null,
            "arr": [1, "two"],
            "obj": {"nested": true}
        });
        let expected = json!({
            "str": {"S": "hello"},
            "num": {"N": "42"},
            "flt": {"N": "3.14"},
            "yes": {"BOOL": true},
            "no": {"BOOL": false},
            "nul": {"NULL": true},
            "arr": {"L": [{"N": "1"}, {"S": "two"}]},
            "obj": {"M": {"nested": {"BOOL": true}}}
        });
        assert_eq!(marshall_top_level(&input), expected);
    }
}
