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

fn get_fact_schema() -> Value {
    json!({
        "type": "object",
        "properties": {
            "predicate": {
                "type": "string"
            },
            "updateView": {
                "type": "string"
            }
        },
        "required": ["predicate", "updateView"]
    })
}

fn get_rule_schema() -> Value {
    json!({
        "title": "Rule Definition (Generalized Boolean Logic)",
        "type": "object",
        "properties": {
            "name": { "type": "string" },
            "evaluate": { "$ref": "#/$defs/logicNode" },
            "variables": {
                "type": "array",
                "items": { "type": "string" }
            },
            "returns": { "enum": ["boolean"] },
            "updateView": {
                "type": "string",
                "enum": ["assert", "retract", "assertz", "asserta"]
            }
        },
        "required": ["name", "evaluate", "variables", "returns"],
        "$defs": {
            "logicNode": {
                "description": "A logical expression node (predicate or combinator)",
                "type": "object",
                "oneOf": [
                    {
                        "description": "A predicate (atomic condition)",
                        "properties": {
                            "predicate": { "type": "string" },
                            "args": {
                                "type": "array",
                                "items": { "type": "string" }
                            }
                        },
                        "required": ["predicate", "args"],
                        "additionalProperties": false
                    },
                    {
                        "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": {
            "predicate": {
                "type": "string"
            }
        },
        "required": ["predicate"]
    })
}

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 value_to_prolog(v: &Value) -> String {
    match v {
        Value::Object(obj) => {
            // Check if this is a variable wrapper: {"var": "VariableName"}
            if let Some(var_name) = obj.get("var") {
                if let Some(name) = var_name.as_str() {
                    return name.to_string(); // Return variable name without quotes
                }
            }
            // If not a variable wrapper, treat as null or could handle nested objects
            "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 convert_logic_node(node: &Value) -> String {
    if let Some(predicate) = node.get("predicate") {
        // Atomic predicate
        let pred_name = predicate.as_str().unwrap();
        if let Some(args) = node.get("args") {
            let arg_list: Vec<String> = args
                .as_array()
                .unwrap()
                .iter()
                .map(|arg| value_to_prolog(arg))
                .collect();
            if arg_list.is_empty() {
                pred_name.to_string()
            } else {
                format!("{}({})", pred_name, arg_list.join(", "))
            }
        } else {
            pred_name.to_string()
        }
    } 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["updateView"].as_str().unwrap().to_string();

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

    entries.sort_by_key(|(key, _)| *key);

    let values: Vec<String> = entries.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 = value["variables"]
        .as_array()
        .unwrap()
        .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 mut entries: Vec<(&String, &Value)> = value
        .as_object()
        .unwrap()
        .iter()
        .filter(|(key, _)| *key != "predicate")
        .collect();

    entries.sort_by_key(|(key, _)| *key);

    let values: Vec<String> = entries.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)
}

#[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#"
        {
            "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#"
        {
            "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#"
        {
            "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#"
        {
            "name": "grandparent",
            "variables": ["X", "Z"],
            "returns": "boolean",
            "updateView": "assert",
            "evaluate": {
                "and": [
                    {"predicate": "parent", "args": ["X", "Y"]},
                    {"predicate": "parent", "args": ["Y", "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",
            "variables": ["X", "Y"],
            "returns": "boolean",
            "evaluate": {
                "or": [
                    {"predicate": "sibling", "args": ["X", "Y"]},
                    {"predicate": "friend", "args": ["X", "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#"
        {
            "name": "not_enemy",
            "variables": ["X", "Y"],
            "returns": "boolean",
            "evaluate": {
                "not": {"predicate": "enemy", "args": ["X", "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_complex_nested() {
        let json_str = r#"
        {
            "name": "complex_rule",
            "variables": ["X"],
            "returns": "boolean",
            "evaluate": {
                "and": [
                    {"predicate": "person", "args": ["X"]},
                    {
                        "or": [
                            {"predicate": "adult", "args": ["X"]},
                            {"not": {"predicate": "child", "args": ["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_convert_query() {
        let json_str = r#"
        {
            "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#"
        {
            "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#"
        {
            "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#"
        {
            "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#"
        {
            "predicate": "person",
            "name": "Dave"
        }
        "#;

        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("updateView"));
    }

    #[test]
    fn test_rule_missing_body() {
        let json_str = r#"
        {
            "name": "grandparent",
            "variables": ["X", "Z"],
            "returns": "boolean"
        }
        "#;

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