use serde_json::{Value, json};
use wasm_bindgen::{JsValue, prelude::wasm_bindgen};

fn get_fact_schema() -> Value {
    json!({
        "type": "object",
        "claimType":{
            "type": "string",
            "enum": ["fact"]
        },
        "properties": {
            "predicate": {
                "type": "string"
            },
            "updateView": {
                "type": "string",
                "enum": ["assert", "retract", "assertz", "asserta"]
            }
        },
        "required": ["predicate", "claimType"]
    })
}

fn get_rule_schema() -> Value {
    json!({
        "title": "Rule Definition (Generalized Boolean Logic)",
        "type": "object",
        "properties": {
            "claimType":{
                "type": "string",
                "enum": ["rule"]
            },
            "name": { "type": "string" },
            "evaluate": { "$ref": "#/$defs/logicNode" },
            "headVariables": {
                "type": "array",
                "items": { "$ref": "#/$defs/prologValue" }
            },
            "updateView": {
                "type": "string",
                "enum": ["assert", "retract", "assertz", "asserta"]
            }
        },
        "required": ["name", "evaluate", "headVariables", "claimType"],
        "$defs": {
            "prologValue": {
                "description": "Any value that can appear in a predicate argument list.",
                "oneOf": [
                    {
                        "type": "object",
                        "properties": {
                            "value": { "type": "string" },
                            "label": { "type": "string" }
                        },
                        "required": ["value"],
                        "additionalProperties": false
                    },
                    {
                        "type": "object",
                        "properties": {
                            "value": { "type": "number" },
                            "label": { "type": "string" }
                        },
                        "required": ["value"],
                        "additionalProperties": false
                    },
                    {
                        "type": "object",
                        "properties": {
                            "value": { "type": "boolean" },
                            "label": { "type": "string" }
                        },
                        "required": ["value"],
                        "additionalProperties": false
                    },
                    {
                        "type": "object",
                        "properties": {
                            "value": { "type": "null" },
                            "label": { "type": "string" }
                        },
                        "required": ["value"],
                        "additionalProperties": false
                    },
                    {
                        "type": "object",
                        "properties": {
                            "var": { "type": "string" },
                            "label": { "type": "string" }
                        },
                        "required": ["var"],
                        "additionalProperties": false
                    },
                    {
                        "type": "object",
                        "properties": {
                            "value": {
                                "type": "array",
                                "items": { "$ref": "#/$defs/prologValue" }
                            },
                            "label": { "type": "string" }
                        },
                        "required": ["value"],
                        "additionalProperties": false
                    },
                    { "$ref": "#/$defs/logicNode" },
                    { "type": "string" },
                    { "type": "number" },
                    { "type": "boolean" },
                    { "type": "null" },
                    {
                        "type": "array",
                        "items": { "$ref": "#/$defs/prologValue" }
                    }
                ]
            },
            "logicNode": {
                "description": "A logical expression node (predicate or combinator)",
                "type": "object",
                "oneOf": [
                    {
                        "description": "A predicate (atomic condition)",
                        "properties": {
                            "predicate": { "type": "string" },
                        },
                        "required": ["predicate"],
                        "additionalProperties": {
                            "$ref": "#/$defs/prologValue"
                        }
                    },
                    {
                        "description": "Logical AND combinator",
                        "properties": {
                            "and": {
                                "type": "array",
                                "items": { "$ref": "#/$defs/logicNode" },
                                "minItems": 1
                            }
                        },
                        "required": ["and"],
                        "additionalProperties": false
                    },
                    {
                        "description": "Logical OR combinator",
                        "properties": {
                            "or": {
                                "type": "array",
                                "items": { "$ref": "#/$defs/logicNode" },
                                "minItems": 1
                            }
                        },
                        "required": ["or"],
                        "additionalProperties": false
                    },
                    {
                        "description": "Logical NOT combinator",
                        "properties": {
                            "not": { "$ref": "#/$defs/logicNode" }
                        },
                        "required": ["not"],
                        "additionalProperties": false
                    }
                ]
            }
        }
    })
}

fn get_query_schema() -> Value {
    json!({
        "type": "object",
        "properties": {
            "claimType":{
                "type": "string",
                "enum": ["query"]
            },
            "predicate": {
                "type": "string"
            }
        },
        "required": ["predicate", "claimType"]
    })
}

fn validate_input(value: &Value, schema: &Value) -> Result<(), String> {
    let validator = jsonschema::validator_for(schema)
        .map_err(|e| format!("Schema compilation error: {}", e))?;

    if let Err(error) = validator.validate(value) {
        return Err(format!("Validation error: {}", error));
    }

    Ok(())
}

fn is_logic_node(obj: &serde_json::Map<String, Value>) -> bool {
    obj.contains_key("predicate")
        || obj.contains_key("and")
        || obj.contains_key("or")
        || obj.contains_key("not")
}

fn value_to_prolog(v: &Value) -> String {
    match v {
        Value::Object(obj) => {
            // Check if this is a logic node (has predicate, and, or, or not fields)
            if is_logic_node(obj) {
                return convert_logic_node(v);
            }
            if let Some(name) = obj.get("var").and_then(|v| v.as_str()) {
                return name.to_string();
            }
            if let Some(value) = obj.get("value") {
                return value_to_prolog(value);
            }
            "null".to_string()
        }
        Value::Null => "_".to_string(),
        Value::String(s) => format!("'{}'", s),
        Value::Number(n) => n.to_string(),
        Value::Bool(b) => b.to_string(),
        Value::Array(arr) => {
            let list_items: Vec<String> = arr.iter().map(|item| value_to_prolog(item)).collect();
            format!("[{}]", list_items.join(", "))
        }
    }
}

fn order_prolog_values_with_keys<'a, I>(values: I) -> Vec<&'a Value>
where
    I: IntoIterator<Item = (&'a str, &'a Value)>,
{
    let mut annotated: Vec<(String, &'a Value)> = values
        .into_iter()
        .map(|(key, value)| {
            let sort_key = value
                .as_object()
                .and_then(|obj| obj.get("label"))
                .and_then(|label_value| label_value.as_str())
                .unwrap_or(key);
            (sort_key.to_string(), value)
        })
        .collect();

    annotated.sort_by(|a, b| a.0.cmp(&b.0));

    annotated.into_iter().map(|(_, value)| value).collect()
}

fn order_prolog_values<'a>(values: &'a [Value]) -> Vec<&'a Value> {
    let mut annotated: Vec<(String, &'a Value)> = values
        .iter()
        .enumerate()
        .map(|(i, v)| {
            let sort_key = v
                .as_object()
                .and_then(|obj| obj.get("label"))
                .and_then(|label_value| label_value.as_str())
                .map(|s| s.to_string())
                .unwrap_or_else(|| i.to_string());
            (sort_key, v)
        })
        .collect();

    annotated.sort_by(|a, b| a.0.cmp(&b.0));

    annotated.into_iter().map(|(_, value)| value).collect()
}

fn convert_logic_node(node: &Value) -> String {
    if let Some(predicate) = node.get("predicate") {
        // Atomic predicate
        let pred_name = predicate.as_str().unwrap();
        let arg_list: Vec<String> = if let Some(obj) = node.as_object() {
            let entries: Vec<(&String, &Value)> =
                obj.iter().filter(|(key, _)| *key != "predicate").collect();
            if entries.is_empty() {
                Vec::new()
            } else {
                order_prolog_values_with_keys(entries.into_iter().map(|(k, v)| (k.as_str(), v)))
                    .into_iter()
                    .map(|arg| value_to_prolog(arg))
                    .collect()
            }
        } else {
            Vec::new()
        };

        if arg_list.is_empty() {
            pred_name.to_string()
        } else {
            format!("{}({})", pred_name, arg_list.join(", "))
        }
    } else if let Some(and_array) = node.get("and") {
        // AND combinator - becomes comma-separated in Prolog
        let terms: Vec<String> = and_array
            .as_array()
            .unwrap()
            .iter()
            .map(|n| convert_logic_node(n))
            .collect();
        format!("({})", terms.join(", "))
    } else if let Some(or_array) = node.get("or") {
        // OR combinator - becomes semicolon-separated in Prolog
        let terms: Vec<String> = or_array
            .as_array()
            .unwrap()
            .iter()
            .map(|n| convert_logic_node(n))
            .collect();
        format!("({})", terms.join(" ; "))
    } else if let Some(not_node) = node.get("not") {
        // NOT combinator - becomes \+ in Prolog
        format!("\\+ {}", convert_logic_node(not_node))
    } else {
        "true".to_string()
    }
}

pub fn convert_fact_to_prolog(value: &Value) -> Result<String, String> {
    validate_input(value, &get_fact_schema())?;

    let predicate: String = value["predicate"].as_str().unwrap().to_string();
    let update_view: String = value
        .get("updateView")
        .and_then(|v| v.as_str())
        .unwrap_or("assert")
        .to_string();

    let entries: Vec<(&String, &Value)> = value
        .as_object()
        .unwrap()
        .iter()
        .filter(|(key, _)| *key != "predicate" && *key != "updateView" && *key != "claimType")
        .collect();

    let values: Vec<String> =
        order_prolog_values_with_keys(entries.into_iter().map(|(k, v)| (k.as_str(), v)))
            .into_iter()
            .map(|v| value_to_prolog(v))
            .collect();

    let head = if values.is_empty() {
        predicate.clone()
    } else {
        let prolog_values = values.join(", ");
        format!("{}({})", predicate, prolog_values)
    };

    Ok(format!("{}({}).", update_view, head))
}

pub fn convert_rule_to_prolog(value: &Value) -> Result<String, String> {
    validate_input(value, &get_rule_schema())?;

    let name: String = value["name"].as_str().unwrap().to_string();
    let update_view: String = value
        .get("updateView")
        .and_then(|v| v.as_str())
        .unwrap_or("assert")
        .to_string();

    let variables = order_prolog_values(value["headVariables"].as_array().unwrap())
        .into_iter()
        .map(|v| value_to_prolog(v))
        .collect::<Vec<String>>();

    let head = if variables.is_empty() {
        name.clone()
    } else {
        format!("{}({})", name, variables.join(", "))
    };

    let body = convert_logic_node(&value["evaluate"]);

    Ok(format!("{}({} :- {}).", update_view, head, body))
}

pub fn convert_query_to_prolog(value: &Value) -> Result<String, String> {
    validate_input(value, &get_query_schema())?;

    let predicate: String = value["predicate"].as_str().unwrap().to_string();

    let entries: Vec<(&String, &Value)> = value
        .as_object()
        .unwrap()
        .iter()
        .filter(|(key, _)| *key != "predicate" && *key != "claimType")
        .collect();

    let values: Vec<String> =
        order_prolog_values_with_keys(entries.into_iter().map(|(k, v)| (k.as_str(), v)))
            .into_iter()
            .map(|v| value_to_prolog(v))
            .collect();

    let query = if values.is_empty() {
        predicate.clone()
    } else {
        let prolog_values = values.join(", ");
        format!("{}({})", predicate, prolog_values)
    };

    Ok(query)
}

pub fn convert_json_to_prolog(value: &Value) -> Result<String, String> {
    if let Some(claim_type) = value.get("claimType").and_then(|v| v.as_str()) {
        match claim_type {
            "fact" => convert_fact_to_prolog(value),
            "rule" => convert_rule_to_prolog(value),
            "query" => convert_query_to_prolog(value),
            _ => Err("Unknown claimType".to_string()),
        }
    } else {
        Err("Missing claimType field".to_string())
    }
}

#[wasm_bindgen]
pub fn convert_json_to_prolog_wasm(value: &JsValue) -> Result<JsValue, JsValue> {
    let serde_value: Value = serde_wasm_bindgen::from_value(value.clone())
        .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
    let prolog_str = convert_json_to_prolog(&serde_value)
        .map_err(|e| JsValue::from_str(&format!("Conversion error: {}", e)))?;
    Ok(JsValue::from_str(&prolog_str))
}

#[wasm_bindgen]
pub fn convert_fact_to_prolog_wasm(value: &JsValue) -> Result<JsValue, JsValue> {
    let serde_value: Value = serde_wasm_bindgen::from_value(value.clone())
        .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
    let prolog_str = convert_fact_to_prolog(&serde_value)
        .map_err(|e| JsValue::from_str(&format!("Conversion error: {}", e)))?;
    Ok(JsValue::from_str(&prolog_str))
}

#[wasm_bindgen]
pub fn convert_rule_to_prolog_wasm(value: &JsValue) -> Result<JsValue, JsValue> {
    let serde_value: Value = serde_wasm_bindgen::from_value(value.clone())
        .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
    let prolog_str = convert_rule_to_prolog(&serde_value)
        .map_err(|e| JsValue::from_str(&format!("Conversion error: {}", e)))?;
    Ok(JsValue::from_str(&prolog_str))
}

#[wasm_bindgen]
pub fn convert_query_to_prolog_wasm(value: &JsValue) -> Result<JsValue, JsValue> {
    let serde_value: Value = serde_wasm_bindgen::from_value(value.clone())
        .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
    let prolog_str = convert_query_to_prolog(&serde_value)
        .map_err(|e| JsValue::from_str(&format!("Conversion error: {}", e)))?;
    Ok(JsValue::from_str(&prolog_str))
}

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

    #[test]
    fn test_convert_fact_to_prolog() {
        let json_str = r#"
        {
            "claimType": "fact",
            "predicate": "person",
            "updateView": "assert",
            "name": "Alice",
            "age": 30,
            "active": true
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let prolog_str = convert_fact_to_prolog(&value).unwrap();
        assert_eq!(prolog_str, "assert(person(true, 30, 'Alice')).");
    }

    #[test]
    fn test_convert_fact_with_lists() {
        let json_str = r#"
        {
            "claimType": "fact",
            "predicate": "person",
            "updateView": "assert",
            "name": "Bob",
            "age": 25,
            "hobbies": ["reading", "coding", "gaming"]
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let prolog_str = convert_fact_to_prolog(&value).unwrap();
        assert_eq!(
            prolog_str,
            "assert(person(25, ['reading', 'coding', 'gaming'], 'Bob'))."
        );
    }

    #[test]
    fn test_convert_fact_with_null_field() {
        let json_str = r#"
        {
            "claimType": "fact",
            "predicate": "person",
            "updateView": "assert",
            "name": "Bob",
            "nickname": null
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let prolog_str = convert_fact_to_prolog(&value).unwrap();
        assert_eq!(prolog_str, "assert(person('Bob', _)).");
    }

    #[test]
    fn test_convert_rule() {
        let json_str = r#"
        {
            "claimType": "rule",
            "name": "grandparent",
            "headVariables": [
                {"var": "X"},
                {"var": "Z"}
            ],
            "updateView": "assert",
            "evaluate": {
                "and": [
                    {
                        "predicate": "parent",
                        "first": {"var": "X"},
                        "second": {"var": "Y"}
                    },
                    {
                        "predicate": "parent",
                        "first": {"var": "Y"},
                        "second": {"var": "Z"}
                    }
                ]
            }
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let prolog_str = convert_rule_to_prolog(&value).unwrap();
        assert_eq!(
            prolog_str,
            "assert(grandparent(X, Z) :- (parent(X, Y), parent(Y, Z)))."
        );
    }

    #[test]
    fn test_convert_rule_with_or() {
        let json_str = r#"
        {
            "claimType": "rule",
            "name": "sibling_or_friend",
            "headVariables": [
                {"var": "X"},
                {"var": "Y"}
            ],
            "evaluate": {
                "or": [
                    {
                        "predicate": "sibling",
                        "first": {"var": "X"},
                        "second": {"var": "Y"}
                    },
                    {
                        "predicate": "friend",
                        "first": {"var": "X"},
                        "second": {"var": "Y"}
                    }
                ]
            }
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let prolog_str = convert_rule_to_prolog(&value).unwrap();
        assert_eq!(
            prolog_str,
            "assert(sibling_or_friend(X, Y) :- (sibling(X, Y) ; friend(X, Y)))."
        );
    }

    #[test]
    fn test_convert_rule_with_not() {
        let json_str = r#"
        {
            "claimType": "rule",
            "name": "not_enemy",
            "headVariables": [
                {"var": "X"},
                {"var": "Y"}
            ],
            "evaluate": {
                "not": {
                    "predicate": "enemy",
                    "first": {"var": "X"},
                    "second": {"var": "Y"}
                }
            }
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let prolog_str = convert_rule_to_prolog(&value).unwrap();
        assert_eq!(prolog_str, "assert(not_enemy(X, Y) :- \\+ enemy(X, Y)).");
    }

    #[test]
    fn test_convert_rule_with_arg_wrapper() {
        let json_str = r#"
        {
            "claimType": "rule",
            "name": "status_checker",
            "headVariables": [{"var": "X"}],
            "evaluate": {
                "predicate": "status",
                "first": {"var": "ready"},
                "second": {"var": "X"}
            }
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let prolog_str = convert_rule_to_prolog(&value).unwrap();
        assert_eq!(prolog_str, "assert(status_checker(X) :- status(ready, X)).");
    }

    #[test]
    fn test_convert_rule_with_named_logic_node_arguments() {
        let json_str = r#"
        {
            "claimType": "rule",
            "name": "named_args_rule",
            "headVariables": [
                {"var": "X"}
            ],
            "evaluate": {
                "predicate": "status",
                "firstArg": {"var": "ready"},
                "secondArg": {"var": "X"}
            }
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let prolog_str = convert_rule_to_prolog(&value).unwrap();
        assert_eq!(
            prolog_str,
            "assert(named_args_rule(X) :- status(ready, X))."
        );
    }

    #[test]
    fn test_named_logic_node_arguments_respect_labels() {
        // Shows that the labels are used to order the arguments in the Prolog rule rather than the variable name
        let json_str = r#"
        {
            "claimType": "rule",
            "name": "labeled_args_rule",
            "headVariables": [
                {"var": "z_Left", "label": "a"},
                {"var": "a_Right", "label": "b"}
            ],
            "evaluate": {
                "predicate": "pair",
                "second": {"var": "a_Right", "label": "b"},
                "first": {"var": "z_Left", "label": "a"}
            }
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let prolog_str = convert_rule_to_prolog(&value).unwrap();
        assert_eq!(
            prolog_str,
            "assert(labeled_args_rule(z_Left, a_Right) :- pair(z_Left, a_Right))."
        );
    }

    #[test]
    fn test_convert_rule_complex_nested() {
        let json_str = r#"
        {
            "claimType": "rule",
            "name": "complex_rule",
            "headVariables": [
                {"var": "X"}
            ],
            "evaluate": {
                "and": [
                    {
                        "predicate": "person",
                        "arg": {"var": "X"}
                    },
                    {
                        "or": [
                            {
                                "predicate": "adult",
                                "arg": {"var": "X"}
                            },
                            {
                                "not": {
                                    "predicate": "child",
                                    "arg": {"var": "X"}
                                }
                            }
                        ]
                    }
                ]
            }
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let prolog_str = convert_rule_to_prolog(&value).unwrap();
        assert_eq!(
            prolog_str,
            "assert(complex_rule(X) :- (person(X), (adult(X) ; \\+ child(X))))."
        );
    }

    #[test]
    fn test_rule_arguments_ordered_by_label() {
        // The labels are used to order the arguments in the Prolog predicate, populate with the fields used in the facts to replicate the ordering
        let json_str = r#"
        {
            "claimType": "rule",
            "name": "ordered_rule",
            "headVariables": [
                {"var": "Second", "label": "b"},
                {"var": "First", "label": "a"}
            ],
            "evaluate": {
                "predicate": "pair",
                "second": {"var": "Right", "label": "b"},
                "first": {"var": "Left", "label": "a"}
            }
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let prolog_str = convert_rule_to_prolog(&value).unwrap();
        assert_eq!(
            prolog_str,
            "assert(ordered_rule(First, Second) :- pair(Left, Right))."
        );
    }

    #[test]
    fn test_convert_query() {
        let json_str = r#"
        {
            "claimType": "query",
            "predicate": "person",
            "name": "Alice",
            "age": {"var": "Age"}
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let prolog_str = convert_query_to_prolog(&value).unwrap();
        assert_eq!(prolog_str, "person(Age, 'Alice')");
    }

    #[test]
    fn test_convert_query_all_variables() {
        let json_str = r#"
        {
            "claimType": "query",
            "predicate": "person",
            "name": {"var": "Name"},
            "age": {"var": "Age"}
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let prolog_str = convert_query_to_prolog(&value).unwrap();
        assert_eq!(prolog_str, "person(Age, Name)");
    }

    #[test]
    fn test_convert_query_with_null_field() {
        let json_str = r#"
        {
            "claimType": "query",
            "predicate": "person",
            "age": {"var": "Age"},
            "name": null
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let prolog_str = convert_query_to_prolog(&value).unwrap();
        assert_eq!(prolog_str, "person(Age, _)");
    }

    #[test]
    fn test_missing_predicate() {
        let json_str = r#"
        {
            "claimType": "fact",
            "updateView": "assert",
            "name": "Charlie"
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let result = convert_fact_to_prolog(&value);
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("predicate"));
    }

    #[test]
    fn test_missing_update_view() {
        let json_str = r#"
        {
            "claimType": "fact",
            "predicate": "person",
            "name": "Dave"
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let result = convert_fact_to_prolog(&value);
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), "assert(person('Dave')).");
    }

    #[test]
    fn test_default_update_view_for_fact() {
        let json_str = r#"
        {
            "claimType": "fact",
            "predicate": "likes",
            "person": "Alice",
            "thing": "coffee"
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let prolog_str = convert_fact_to_prolog(&value).unwrap();
        assert_eq!(prolog_str, "assert(likes('Alice', 'coffee')).");
    }

    #[test]
    fn test_explicit_retract_update_view() {
        let json_str = r#"
        {
            "claimType": "fact",
            "predicate": "likes",
            "updateView": "retract",
            "person": "Bob",
            "thing": "tea"
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let prolog_str = convert_fact_to_prolog(&value).unwrap();
        assert_eq!(prolog_str, "retract(likes('Bob', 'tea')).");
    }

    #[test]
    fn test_invalid_update_view() {
        let json_str = r#"
        {
            "claimType": "fact",
            "predicate": "person",
            "updateView": "invalid_action",
            "name": "Eve"
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let result = convert_fact_to_prolog(&value);
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("Validation error"));
    }

    #[test]
    fn test_rule_missing_body() {
        let json_str = r#"
        {
            "claimType": "rule",
            "name": "grandparent",
            "headVariables": [
                {"var": "X"},
                {"var": "Z"}
            ]
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let result = convert_rule_to_prolog(&value);
        assert!(result.is_err());
    }

    #[test]
    fn test_convert_json_to_prolog_fact() {
        let json_str = r#"
        {
            "claimType": "fact",
            "predicate": "person",
            "updateView": "assert",
            "name": "Alice",
            "age": 30
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let prolog_str = convert_json_to_prolog(&value).unwrap();
        assert_eq!(prolog_str, "assert(person(30, 'Alice')).");
    }

    #[test]
    fn test_convert_json_to_prolog_rule() {
        let json_str = r#"
        {
            "claimType": "rule",
            "name": "sibling",
            "headVariables": [
                {"var": "X"},
                {"var": "Y"}
            ],
            "evaluate": {
                "and": [
                    {
                        "predicate": "parent",
                        "first": {"var": "P"},
                        "second": {"var": "X"}
                    },
                    {
                        "predicate": "parent",
                        "first": {"var": "P"},
                        "second": {"var": "Y"}
                    }
                ]
            }
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let prolog_str = convert_json_to_prolog(&value).unwrap();
        assert_eq!(
            prolog_str,
            "assert(sibling(X, Y) :- (parent(P, X), parent(P, Y)))."
        );
    }

    #[test]
    fn test_convert_json_to_prolog_query() {
        let json_str = r#"
        {
            "claimType": "query",
            "predicate": "person",
            "name": "Alice",
            "age": {"var": "Age"}
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let prolog_str = convert_json_to_prolog(&value).unwrap();
        assert_eq!(prolog_str, "person(Age, 'Alice')");
    }

    #[test]
    fn test_convert_json_to_prolog_missing_claim_type() {
        let json_str = r#"
        {
            "predicate": "person",
            "name": "Alice"
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let result = convert_json_to_prolog(&value);
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("claimType"));
    }

    #[test]
    fn test_convert_json_to_prolog_invalid_claim_type() {
        let json_str = r#"
        {
            "claimType": "unknown",
            "predicate": "person",
            "name": "Alice"
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let result = convert_json_to_prolog(&value);
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("Unknown claimType"));
    }

    #[test]
    fn test_fact_with_labeled_string() {
        let json_str = r#"
        {
            "claimType": "fact",
            "predicate": "person",
            "updateView": "assert",
            "name": {"value": "Alice", "label": "z_name"},
            "age": {"value": 30, "label": "a_age"}
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let prolog_str = convert_fact_to_prolog(&value).unwrap();
        // Should be ordered by label: a_age comes before z_name
        assert_eq!(prolog_str, "assert(person(30, 'Alice')).");
    }

    #[test]
    fn test_fact_with_labeled_boolean_and_null() {
        let json_str = r#"
        {
            "claimType": "fact",
            "predicate": "status",
            "active": {"value": true, "label": "b"},
            "note": {"value": null, "label": "a"}
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let prolog_str = convert_fact_to_prolog(&value).unwrap();
        // Ordered by label: a before b
        assert_eq!(prolog_str, "assert(status(_, true)).");
    }

    #[test]
    fn test_fact_with_labeled_array() {
        let json_str = r#"
        {
            "claimType": "fact",
            "predicate": "data",
            "items": {"value": [1, 2, 3], "label": "z"},
            "name": {"value": "test", "label": "a"}
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let prolog_str = convert_fact_to_prolog(&value).unwrap();
        // Ordered by label: a before z
        assert_eq!(prolog_str, "assert(data('test', [1, 2, 3])).");
    }

    #[test]
    fn test_rule_with_labeled_values_in_head() {
        let json_str = r#"
        {
            "claimType": "rule",
            "name": "labeled_rule",
            "headVariables": [
                {"value": "constant", "label": "z"},
                {"var": "X", "label": "a"}
            ],
            "returns": "boolean",
            "evaluate": {
                "predicate": "test",
                "arg": {"var": "X"}
            }
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let prolog_str = convert_rule_to_prolog(&value).unwrap();
        // Head variables ordered by label: a before z
        assert_eq!(
            prolog_str,
            "assert(labeled_rule(X, 'constant') :- test(X))."
        );
    }

    #[test]
    fn test_rule_with_labeled_numbers_in_predicate() {
        let json_str = r#"
        {
            "claimType": "rule",
            "name": "math_rule",
            "headVariables": [{"var": "X"}],
            "returns": "boolean",
            "evaluate": {
                "predicate": "between",
                "high": {"value": 100, "label": "c"},
                "low": {"value": 0, "label": "a"},
                "val": {"var": "X", "label": "b"}
            }
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let prolog_str = convert_rule_to_prolog(&value).unwrap();
        // Predicate args ordered by label: a, b, c
        assert_eq!(prolog_str, "assert(math_rule(X) :- between(0, X, 100)).");
    }

    #[test]
    fn test_query_with_labeled_values() {
        let json_str = r#"
        {
            "claimType": "query",
            "predicate": "person",
            "name": {"value": "Alice", "label": "z"},
            "age": {"var": "Age", "label": "a"}
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let prolog_str = convert_query_to_prolog(&value).unwrap();
        // Ordered by label: a before z
        assert_eq!(prolog_str, "person(Age, 'Alice')");
    }

    #[test]
    fn test_mixed_labeled_and_unlabeled_values() {
        let json_str = r#"
        {
            "claimType": "fact",
            "predicate": "mixed",
            "updateView": "assert",
            "unlabeled": "plain_string",
            "labeled": {"value": "wrapped_string", "label": "a"}
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let prolog_str = convert_fact_to_prolog(&value).unwrap();
        // Labeled comes first, then unlabeled in alphabetical order
        assert_eq!(
            prolog_str,
            "assert(mixed('wrapped_string', 'plain_string'))."
        );
    }

    #[test]
    fn test_rule_with_mixed_labeled_and_unlabeled_properties() {
        // Demonstrates mixing labeled and unlabeled properties in rule predicates
        let json_str = r#"
        {
            "claimType": "rule",
            "name": "mixed_args_rule",
            "headVariables": [
                {"var": "X"},
                {"var": "Y"}
            ],
            "evaluate": {
                "predicate": "process",
                "1": {"var": "X"},
                "foo": {"var": "Y", "label": "2"},
                "3": "Henry",
                "bar": {"value": 42, "label": "4"}
            }
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let prolog_str = convert_rule_to_prolog(&value).unwrap();
        // Labeled properties come first (sorted by label: a, b)
        // Then unlabeled properties (sorted alphabetically: unlabeled_a, unlabeled_b)
        assert_eq!(
            prolog_str,
            "assert(mixed_args_rule(X, Y) :- process(X, Y, 'Henry', 42))."
        );
    }

    #[test]
    fn test_rule_with_mixed_labeled_and_unlabeled_properties_2() {
        // Demonstrates mixing labeled and unlabeled properties in rule predicates
        // If the ordering was just on the keys, we'd have a different order, shows labels have priority
        let json_str = r#"
        {
            "claimType": "rule",
            "name": "mixed_args_rule",
            "headVariables": [
                {"var": "X"},
                {"var": "Y"}
            ],
            "evaluate": {
                "predicate": "process",
                "1": {"var": "X"},
                "5": {"var": "Y", "label": "2"},
                "3": "Henry",
                "0": {"value": 42, "label": "4"}
            }
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let prolog_str = convert_rule_to_prolog(&value).unwrap();
        // Labeled properties come first (sorted by label: a, b)
        // Then unlabeled properties (sorted alphabetically: unlabeled_a, unlabeled_b)
        assert_eq!(
            prolog_str,
            "assert(mixed_args_rule(X, Y) :- process(X, Y, 'Henry', 42))."
        );
    }

    #[test]
    fn test_rule_with_findall() {
        let json_str = r#"
        {
            "claimType": "rule",
            "name": "foo",
            "headVariables": [
                {"var": "_Name"},
                {"var": "People"}
            ],
            "evaluate": {
                "predicate": "findall",
                "a_template": {"var": "_Name"},
                "b_goal": {
                    "predicate": "person",
                    "name": {"var": "_Name"}
                },
                "c_result": {"var": "People"}
            }
        }
        "#;

        let value: Value = serde_json::from_str(json_str).unwrap();
        let prolog_str = convert_rule_to_prolog(&value).unwrap();
        assert_eq!(
            prolog_str,
            "assert(foo(_Name, People) :- findall(_Name, person(_Name), People))."
        );
    }
}
