/// Migration interpreter — port of PHP `Migration_Interpreter`.
///
/// Applies a list of `set` / `delete` / `move` operations (with optional conditions)
/// to a `serde_json::Value` element data tree.
///
/// Migration schema shape:
/// ```json
/// {
///   "up": [
///     {"op": {"fn": "set", "path": "props.color", "value": {"$$type":"color","value":"red"}}},
///     {"op": {"fn": "delete", "path": "props.old_key"}},
///     {"op": {"fn": "move", "src": "props.a", "dest": "props.b"},
///      "condition": {"fn": "exists", "path": "props.a"}}
///   ],
///   "down": [ ... ]
/// }
/// ```

use crate::path_resolver::{self, PathKey, WildcardBinding};
use serde_json::{json, Map, Value};
use thiserror::Error;

#[derive(Debug, Error)]
pub enum MigrationError {
    #[error("Invalid direction '{0}': must be 'up' or 'down'")]
    InvalidDirection(String),
    #[error("Move operation missing 'src' or 'dest'")]
    MissingMovePaths,
    #[error("Malformed path: {0}")]
    MalformedPath(String),
}

pub type Result<T> = std::result::Result<T, MigrationError>;

/// Run a migration schema (up or down) against `element_data`.
pub fn run(schema: &Value, mut data: Value, direction: &str) -> Result<Value> {
    if direction != "up" && direction != "down" {
        return Err(MigrationError::InvalidDirection(direction.to_string()));
    }

    let ops = match schema.get(direction).and_then(|v| v.as_array()) {
        Some(ops) => ops,
        None => return Ok(data),
    };

    for op_def in ops {
        data = apply_operation(op_def, data)?;
    }

    Ok(data)
}

fn apply_operation(op_def: &Value, mut data: Value) -> Result<Value> {
    let op = match op_def.get("op") {
        Some(o) => o,
        None => return Ok(data),
    };

    let fn_name = match op.get("fn").and_then(|v| v.as_str()) {
        Some(f) => f,
        None => return Ok(data),
    };

    if fn_name == "move" {
        execute_move(op, &mut data)?;
        return Ok(data);
    }

    let path_pattern = match op.get("path").and_then(|v| v.as_str()) {
        Some(p) => p,
        None => return Ok(data),
    };

    let has_wildcard = path_pattern.contains('*');
    let mut resolved = path_resolver::resolve(path_pattern, &data, true);

    if resolved.is_empty() && fn_name == "set" && !has_wildcard {
        resolved = vec![crate::path_resolver::PathMatch {
            path: path_pattern.to_string(),
            wildcard_values: vec![],
        }];
    }

    let condition = op_def.get("condition");

    for path_info in &resolved {
        if let Some(cond) = condition {
            if !check_condition(cond, &path_info.wildcard_values, &data) {
                continue;
            }
        }
        execute_operation(fn_name, op, &path_info.path, &mut data)?;
    }

    Ok(data)
}

fn execute_operation(fn_name: &str, op: &Value, path: &str, data: &mut Value) -> Result<()> {
    match fn_name {
        "set" => execute_set(op, path, data),
        "delete" => {
            let clean = op.get("clean").and_then(|v| v.as_bool()).unwrap_or(true);
            delete_at(data, path, clean);
            Ok(())
        }
        "move" => execute_move(op, data),
        _ => Ok(()),
    }
}

fn execute_set(op: &Value, path: &str, data: &mut Value) -> Result<()> {
    let has_value = op.get("value").is_some();
    let has_key = op.get("key").and_then(|v| v.as_str()).is_some();
    let merge = op.get("merge").and_then(|v| v.as_bool()).unwrap_or(true);

    let mut current_path = path.to_string();

    if has_key {
        let new_key = op["key"].as_str().unwrap();
        rename_key(data, path, new_key);
        current_path = renamed_path(path, new_key);
    }

    if has_value {
        let current = path_resolver::get(&current_path, data).cloned();
        let new_val = resolve_value(&op["value"], current.as_ref());

        let final_val = if merge {
            match (current.as_ref(), &new_val) {
                (Some(Value::Object(base)), Value::Object(overlay)) => {
                    Value::Object(deep_merge(base.clone(), overlay.clone()))
                }
                _ => new_val,
            }
        } else {
            new_val
        };

        set_at(data, &current_path, final_val);
    } else if !has_key {
        set_at(data, &current_path, json!({}));
    }

    Ok(())
}

fn execute_move(op: &Value, data: &mut Value) -> Result<()> {
    let src = op.get("src").and_then(|v| v.as_str()).ok_or(MigrationError::MissingMovePaths)?;
    let dest = op.get("dest").and_then(|v| v.as_str()).ok_or(MigrationError::MissingMovePaths)?;
    let clean = op.get("clean").and_then(|v| v.as_bool()).unwrap_or(true);

    let value = match path_resolver::get(src, data).cloned() {
        Some(v) => v,
        None => return Ok(()),
    };

    set_at(data, dest, value);

    if clean {
        delete_at(data, src, true);
    }

    Ok(())
}

// ─── Condition evaluation ─────────────────────────────────────────────────────

fn check_condition(cond: &Value, wc: &[WildcardBinding], data: &Value) -> bool {
    let fn_name = match cond.get("fn").and_then(|v| v.as_str()) {
        Some(f) => f,
        None => return true,
    };

    if fn_name == "and" {
        return cond
            .get("conditions")
            .and_then(|v| v.as_array())
            .map(|cs| cs.iter().all(|c| check_condition(c, wc, data)))
            .unwrap_or(true);
    }

    if fn_name == "or" {
        return cond
            .get("conditions")
            .and_then(|v| v.as_array())
            .map(|cs| cs.iter().any(|c| check_condition(c, wc, data)))
            .unwrap_or(false);
    }

    let path_pattern = match cond.get("path").and_then(|v| v.as_str()) {
        Some(p) => p,
        None => return false,
    };

    let resolved_path = path_resolver::resolve_with_wildcard_binding(path_pattern, data, wc);

    if resolved_path.is_none() {
        return fn_name == "not_exists";
    }

    let resolved_path = resolved_path.unwrap();
    let value = path_resolver::get(&resolved_path, data);
    let expected = cond.get("value");

    eval_condition(fn_name, value, expected)
}

fn eval_condition(fn_name: &str, value: Option<&Value>, expected: Option<&Value>) -> bool {
    match fn_name {
        "equals" => value == expected,
        "not_equals" => value != expected,
        "exists" => value.is_some(),
        "not_exists" => value.is_none(),
        "in" => {
            let arr = expected.and_then(|v| v.as_array());
            arr.map(|a| a.contains(value.unwrap_or(&Value::Null))).unwrap_or(false)
        }
        "not_in" => {
            let arr = expected.and_then(|v| v.as_array());
            arr.map(|a| !a.contains(value.unwrap_or(&Value::Null))).unwrap_or(true)
        }
        "is_primitive" => value.map(|v| v.is_string() || v.is_number() || v.is_boolean()).unwrap_or(false),
        "is_object" => value.map(|v| v.is_object()).unwrap_or(false),
        "is_array" => value.map(|v| v.is_array()).unwrap_or(false),
        _ => false,
    }
}

// ─── Value resolution ($$current references) ──────────────────────────────────

fn resolve_value(val_def: &Value, current: Option<&Value>) -> Value {
    if !is_reference(val_def) {
        return val_def.clone();
    }

    match val_def {
        Value::String(s) => resolve_string_ref(s, current),
        Value::Array(arr) => Value::Array(arr.iter().map(|item| resolve_value(item, current)).collect()),
        Value::Object(obj) => Value::Object(
            obj.iter()
                .map(|(k, v)| (k.clone(), resolve_value(v, current)))
                .collect(),
        ),
        _ => val_def.clone(),
    }
}

fn is_reference(val: &Value) -> bool {
    match val {
        Value::String(s) => s == "$$current" || s.starts_with("$$current."),
        Value::Array(arr) => arr.iter().any(is_reference),
        Value::Object(obj) => obj.values().any(is_reference),
        _ => false,
    }
}

fn resolve_string_ref(s: &str, current: Option<&Value>) -> Value {
    if s == "$$current" {
        return current.cloned().unwrap_or(Value::Null);
    }
    if let Some(path) = s.strip_prefix("$$current.") {
        return get_nested(current, path);
    }
    Value::String(s.to_string())
}

fn get_nested(data: Option<&Value>, path: &str) -> Value {
    let mut current = match data {
        Some(v) => v,
        None => return Value::Null,
    };
    for key in path.split('.') {
        match current.get(key) {
            Some(v) => current = v,
            None => return Value::Null,
        }
    }
    current.clone()
}

// ─── Mutable set / delete / rename at path ────────────────────────────────────

fn set_at(data: &mut Value, path: &str, value: Value) {
    let segments = parse_path_segments(path);
    set_recursive(data, &segments, value);
}

fn set_recursive(data: &mut Value, segs: &[String], value: Value) {
    if segs.is_empty() {
        return;
    }
    let (head, tail) = segs.split_first().unwrap();

    if head == "[]" {
        if tail.is_empty() {
            if let Some(arr) = data.as_array_mut() {
                arr.push(value);
            }
        } else {
            if let Some(arr) = data.as_array_mut() {
                let new_idx = arr.len();
                arr.push(json!({}));
                set_recursive(&mut arr[new_idx], tail, value);
            }
        }
        return;
    }

    if tail.is_empty() {
        match data {
            Value::Object(obj) => { obj.insert(head.clone(), value); }
            Value::Array(arr) => {
                if let Ok(i) = head.parse::<usize>() {
                    if i < arr.len() { arr[i] = value; }
                }
            }
            _ => {}
        }
        return;
    }

    match data {
        Value::Object(obj) => {
            let entry = obj.entry(head.clone()).or_insert_with(|| json!({}));
            set_recursive(entry, tail, value);
        }
        Value::Array(arr) => {
            if let Ok(i) = head.parse::<usize>() {
                if i < arr.len() {
                    set_recursive(&mut arr[i], tail, value);
                }
            }
        }
        _ => {}
    }
}

fn delete_at(data: &mut Value, path: &str, _clean: bool) {
    let segs = parse_path_segments(path);
    delete_recursive(data, &segs);
}

fn delete_recursive(data: &mut Value, segs: &[String]) {
    if segs.is_empty() {
        return;
    }
    let (head, tail) = segs.split_first().unwrap();

    if tail.is_empty() {
        match data {
            Value::Object(obj) => { obj.remove(head.as_str()); }
            Value::Array(arr) => {
                if let Ok(i) = head.parse::<usize>() {
                    if i < arr.len() { arr.remove(i); }
                }
            }
            _ => {}
        }
        return;
    }

    match data {
        Value::Object(obj) => {
            if let Some(child) = obj.get_mut(head.as_str()) {
                delete_recursive(child, tail);
            }
        }
        Value::Array(arr) => {
            if let Ok(i) = head.parse::<usize>() {
                if i < arr.len() {
                    delete_recursive(&mut arr[i], tail);
                }
            }
        }
        _ => {}
    }
}

fn rename_key(data: &mut Value, path: &str, new_key: &str) {
    let segs = parse_path_segments(path);
    rename_recursive(data, &segs, new_key);
}

fn rename_recursive(data: &mut Value, segs: &[String], new_key: &str) {
    if segs.is_empty() {
        return;
    }
    let (head, tail) = segs.split_first().unwrap();

    if tail.is_empty() {
        if let Value::Object(obj) = data {
            if let Some(val) = obj.remove(head.as_str()) {
                obj.insert(new_key.to_string(), val);
            }
        }
        return;
    }

    match data {
        Value::Object(obj) => {
            if let Some(child) = obj.get_mut(head.as_str()) {
                rename_recursive(child, tail, new_key);
            }
        }
        _ => {}
    }
}

fn renamed_path(path: &str, new_key: &str) -> String {
    let last_dot = path.rfind('.');
    let last_bracket = path.rfind('[');

    match (last_dot, last_bracket) {
        (None, None) => new_key.to_string(),
        (Some(d), None) => format!("{}.{}", &path[..d], new_key),
        (None, Some(b)) => {
            // replace inside bracket
            let close = path.find(']').unwrap_or(path.len());
            format!("{}[{}{}", &path[..b + 1], new_key, &path[close..])
        }
        (Some(d), Some(b)) => {
            if b > d {
                let close = path[b..].find(']').map(|p| b + p).unwrap_or(path.len());
                format!("{}[{}{}", &path[..b + 1], new_key, &path[close..])
            } else {
                format!("{}.{}", &path[..d], new_key)
            }
        }
    }
}

fn parse_path_segments(path: &str) -> Vec<String> {
    let mut segs = Vec::new();
    let mut current = String::new();
    let chars: Vec<char> = path.chars().collect();
    let mut i = 0;

    while i < chars.len() {
        match chars[i] {
            '.' => {
                if !current.is_empty() {
                    segs.push(std::mem::take(&mut current));
                }
            }
            '[' => {
                if !current.is_empty() {
                    segs.push(std::mem::take(&mut current));
                }
                let end = path[i..].find(']').map(|p| i + p);
                if let Some(end) = end {
                    let index = path[i + 1..end].to_string();
                    segs.push(if index.is_empty() { "[]".to_string() } else { index });
                    i = end;
                }
            }
            c => current.push(c),
        }
        i += 1;
    }
    if !current.is_empty() {
        segs.push(current);
    }
    segs
}

fn deep_merge(mut base: Map<String, Value>, overlay: Map<String, Value>) -> Map<String, Value> {
    for (key, val) in overlay {
        let entry = base.entry(key).or_insert(Value::Null);
        match (entry.clone(), &val) {
            (Value::Object(base_obj), Value::Object(over_obj)) => {
                *entry = Value::Object(deep_merge(base_obj, over_obj.clone()));
            }
            _ => *entry = val,
        }
    }
    base
}
