//! Conversion of Python operation dicts to `aerospike_core::Operation` values.
//!
//! Each operation is represented as a Python dict with at minimum an `"op"` key
//! (integer operation code). This module dispatches on that code to construct
//! the corresponding Rust `Operation` for basic CRUD, List CDT, and Map CDT ops.

use aerospike_core::{
    operations,
    operations::bitwise::{self as bit_ops, BitPolicy, BitwiseOverflowActions, BitwiseResizeFlags},
    operations::hll::{self as hll_ops, HLLPolicy},
    operations::lists::{
        self as list_ops, ListOrderType, ListPolicy, ListReturnType, ListSortFlags,
    },
    operations::maps::{self as map_ops, MapOrder, MapPolicy, MapReturnType, MapWriteMode},
    operations::Operation,
    Bin, Value,
};
use log::trace;
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyList};

use crate::constants::*;
use crate::types::value::py_to_value;

// ── Helper functions ────────────────────────────────────────────

/// Require a bin name, returning a descriptive error if absent.
fn require_bin(bin_name: &Option<String>, op_name: &str) -> PyResult<String> {
    bin_name.clone().ok_or_else(|| {
        pyo3::exceptions::PyValueError::new_err(format!("{op_name} operation requires 'bin'"))
    })
}

fn get_index(dict: &Bound<'_, PyDict>) -> PyResult<i64> {
    dict.get_item("index")?
        .ok_or_else(|| pyo3::exceptions::PyValueError::new_err("Operation requires 'index'"))?
        .extract()
}

fn get_rank(dict: &Bound<'_, PyDict>) -> PyResult<i64> {
    // Try "rank" key first, fall back to "index" for backward compatibility
    if let Some(v) = dict.get_item("rank")? {
        return v.extract();
    }
    dict.get_item("index")?
        .ok_or_else(|| {
            pyo3::exceptions::PyValueError::new_err("Operation requires 'rank' or 'index'")
        })?
        .extract()
}

fn get_count(dict: &Bound<'_, PyDict>) -> PyResult<Option<i64>> {
    dict.get_item("count")?
        .and_then(|v| if v.is_none() { None } else { Some(v) })
        .map(|v| v.extract())
        .transpose()
}

fn get_return_type(dict: &Bound<'_, PyDict>) -> PyResult<i32> {
    dict.get_item("return_type")?
        .ok_or_else(|| pyo3::exceptions::PyValueError::new_err("Operation requires 'return_type'"))?
        .extract()
}

fn get_map_key(dict: &Bound<'_, PyDict>) -> PyResult<Value> {
    let v = dict
        .get_item("map_key")?
        .ok_or_else(|| pyo3::exceptions::PyValueError::new_err("Operation requires 'map_key'"))?;
    py_to_value(&v)
}

fn get_val_end(dict: &Bound<'_, PyDict>) -> PyResult<Value> {
    dict.get_item("val_end")?
        .and_then(|v| if v.is_none() { None } else { Some(v) })
        .map(|v| py_to_value(&v))
        .transpose()
        .map(|v| v.unwrap_or(Value::Infinity))
}

/// Map a Python integer to a [`ListReturnType`] enum variant.
fn int_to_list_return_type(v: i32) -> ListReturnType {
    match v {
        0 => ListReturnType::None,
        1 => ListReturnType::Index,
        2 => ListReturnType::ReverseIndex,
        3 => ListReturnType::Rank,
        4 => ListReturnType::ReverseRank,
        5 => ListReturnType::Count,
        7 => ListReturnType::Values,
        13 => ListReturnType::Exists,
        _ => ListReturnType::None,
    }
}

/// Map a Python integer to a [`MapReturnType`] enum variant.
fn int_to_map_return_type(v: i32) -> MapReturnType {
    match v {
        0 => MapReturnType::None,
        1 => MapReturnType::Index,
        2 => MapReturnType::ReverseIndex,
        3 => MapReturnType::Rank,
        4 => MapReturnType::ReverseRank,
        5 => MapReturnType::Count,
        6 => MapReturnType::Key,
        7 => MapReturnType::Value,
        8 => MapReturnType::KeyValue,
        13 => MapReturnType::Exists,
        _ => MapReturnType::None,
    }
}

/// Parse an optional `list_policy` sub-dict from an operation dict.
fn parse_list_policy(dict: &Bound<'_, PyDict>) -> PyResult<ListPolicy> {
    if let Some(policy_obj) = dict.get_item("list_policy")? {
        if policy_obj.is_none() {
            return Ok(ListPolicy::default());
        }
        let policy_dict = policy_obj.cast::<PyDict>()?;
        let order: i32 = policy_dict
            .get_item("order")?
            .map(|v| v.extract())
            .transpose()?
            .unwrap_or(0);
        let flags: u8 = policy_dict
            .get_item("flags")?
            .map(|v| v.extract())
            .transpose()?
            .unwrap_or(0);
        let order_type = match order {
            1 => ListOrderType::Ordered,
            _ => ListOrderType::Unordered,
        };
        Ok(ListPolicy {
            attributes: order_type,
            flags,
        })
    } else {
        Ok(ListPolicy::default())
    }
}

/// Parse an optional `map_policy` sub-dict from an operation dict.
fn parse_map_policy(dict: &Bound<'_, PyDict>) -> PyResult<MapPolicy> {
    if let Some(policy_obj) = dict.get_item("map_policy")? {
        if policy_obj.is_none() {
            return Ok(MapPolicy::default());
        }
        let policy_dict = policy_obj.cast::<PyDict>()?;
        let order: i32 = policy_dict
            .get_item("order")?
            .map(|v| v.extract())
            .transpose()?
            .unwrap_or(0);
        let write_mode: i32 = policy_dict
            .get_item("write_mode")?
            .map(|v| v.extract())
            .transpose()?
            .unwrap_or(0);
        let map_order = match order {
            1 => MapOrder::KeyOrdered,
            3 => MapOrder::KeyValueOrdered,
            _ => MapOrder::Unordered,
        };
        let mode = match write_mode {
            1 => MapWriteMode::CreateOnly,
            2 => MapWriteMode::UpdateOnly,
            _ => MapWriteMode::Update,
        };
        Ok(MapPolicy::new(map_order, mode))
    } else {
        Ok(MapPolicy::default())
    }
}

/// Parse an optional `hll_policy` sub-dict from an operation dict.
fn parse_hll_policy(dict: &Bound<'_, PyDict>) -> PyResult<HLLPolicy> {
    if let Some(policy_obj) = dict.get_item("hll_policy")? {
        if policy_obj.is_none() {
            return Ok(HLLPolicy::default());
        }
        let policy_dict = policy_obj.cast::<PyDict>()?;
        let flags: i64 = policy_dict
            .get_item("flags")?
            .map(|v| v.extract())
            .transpose()?
            .unwrap_or(0);
        Ok(HLLPolicy { flags })
    } else {
        Ok(HLLPolicy::default())
    }
}

/// Parse a `BitPolicy` from an operation dict's `"bit_policy"` key.
fn parse_bit_policy(dict: &Bound<'_, PyDict>) -> PyResult<BitPolicy> {
    if let Some(flags_obj) = dict.get_item("bit_policy")? {
        if flags_obj.is_none() {
            return Ok(BitPolicy::default());
        }
        let flags: u8 = flags_obj.extract()?;
        Ok(BitPolicy::new(flags))
    } else {
        Ok(BitPolicy::default())
    }
}

fn get_bit_offset(dict: &Bound<'_, PyDict>) -> PyResult<i64> {
    dict.get_item("bit_offset")?
        .ok_or_else(|| {
            pyo3::exceptions::PyValueError::new_err("Bit operation requires 'bit_offset'")
        })?
        .extract()
}

fn get_bit_size(dict: &Bound<'_, PyDict>) -> PyResult<i64> {
    dict.get_item("bit_size")?
        .ok_or_else(|| {
            pyo3::exceptions::PyValueError::new_err("Bit operation requires 'bit_size'")
        })?
        .extract()
}

fn get_byte_size(dict: &Bound<'_, PyDict>) -> PyResult<i64> {
    dict.get_item("byte_size")?
        .ok_or_else(|| {
            pyo3::exceptions::PyValueError::new_err("Bit operation requires 'byte_size'")
        })?
        .extract()
}

fn get_byte_offset(dict: &Bound<'_, PyDict>) -> PyResult<i64> {
    dict.get_item("byte_offset")?
        .ok_or_else(|| {
            pyo3::exceptions::PyValueError::new_err("Bit operation requires 'byte_offset'")
        })?
        .extract()
}

fn get_shift(dict: &Bound<'_, PyDict>) -> PyResult<i64> {
    dict.get_item("shift")?
        .ok_or_else(|| {
            pyo3::exceptions::PyValueError::new_err("Bit shift operation requires 'shift'")
        })?
        .extract()
}

fn get_signed(dict: &Bound<'_, PyDict>) -> PyResult<bool> {
    match dict.get_item("signed")? {
        Some(v) => v.extract(),
        None => Ok(false),
    }
}

fn get_overflow_action(dict: &Bound<'_, PyDict>) -> PyResult<BitwiseOverflowActions> {
    let action: i32 = dict
        .get_item("action")?
        .map(|v| v.extract())
        .transpose()?
        .unwrap_or(0);
    Ok(match action {
        2 => BitwiseOverflowActions::Saturate,
        4 => BitwiseOverflowActions::Wrap,
        _ => BitwiseOverflowActions::Fail,
    })
}

fn get_resize_flags(dict: &Bound<'_, PyDict>) -> PyResult<Option<BitwiseResizeFlags>> {
    let flags: Option<i32> = dict
        .get_item("resize_flags")?
        .map(|v| v.extract())
        .transpose()?;
    Ok(flags.map(|f| match f {
        1 => BitwiseResizeFlags::FromFront,
        2 => BitwiseResizeFlags::GrowOnly,
        4 => BitwiseResizeFlags::ShrinkOnly,
        _ => BitwiseResizeFlags::Default,
    }))
}

fn get_scan_value(dict: &Bound<'_, PyDict>) -> PyResult<bool> {
    dict.get_item("val")?
        .map(|v| v.extract())
        .transpose()?
        .ok_or_else(|| {
            pyo3::exceptions::PyValueError::new_err("Bit scan operation requires 'val' (bool)")
        })
}

/// Unwrap a `Value::List` into its inner `Vec`, or wrap a single value in a `Vec`.
fn values_from_list(val: &Value) -> Vec<Value> {
    match val {
        Value::List(v) => v.clone(),
        _ => vec![val.clone()],
    }
}

/// Parse an operation flag value that should be a small integer (i32).
///
/// Missing/None values default to `0`.
fn parse_i32_flag(val: &Option<Value>, op_name: &str, field_name: &str) -> PyResult<i32> {
    match val {
        None | Some(Value::Nil) => Ok(0),
        Some(Value::Int(i)) => i32::try_from(*i).map_err(|_| {
            pyo3::exceptions::PyValueError::new_err(format!(
                "{op_name} operation '{field_name}' must fit in i32 range, got {i}"
            ))
        }),
        Some(other) => Err(pyo3::exceptions::PyTypeError::new_err(format!(
            "{op_name} operation '{field_name}' must be int, got {other:?}"
        ))),
    }
}

// ── Main conversion ─────────────────────────────────────────────

/// Convert a Python list of operation dicts to Rust Operations.
/// Each operation is a dict: {"op": int, "bin": str, "val": any, ...}
pub fn py_ops_to_rust(ops_list: &Bound<'_, PyList>) -> PyResult<Vec<Operation>> {
    trace!("Converting {} Python operations to Rust", ops_list.len());
    let mut rust_ops: Vec<Operation> = Vec::with_capacity(ops_list.len());

    for item in ops_list.iter() {
        let dict = item.cast::<PyDict>()?;

        let op_code: i32 = dict
            .get_item("op")?
            .ok_or_else(|| pyo3::exceptions::PyValueError::new_err("Operation must have 'op' key"))?
            .extract()?;

        let bin_name: Option<String> = dict
            .get_item("bin")?
            .and_then(|v| if v.is_none() { None } else { Some(v) })
            .map(|v| v.extract())
            .transpose()?;

        let val: Option<Value> = dict
            .get_item("val")?
            .and_then(|v| if v.is_none() { None } else { Some(v) })
            .map(|v| py_to_value(&v))
            .transpose()?;

        let op = match op_code {
            // ── Basic operations ─────────────────────────────
            OP_READ => {
                if let Some(name) = &bin_name {
                    operations::get_bin(name)
                } else {
                    operations::get()
                }
            }
            OP_WRITE => {
                let name = require_bin(&bin_name, "Write")?;
                let v = val.unwrap_or(Value::Nil);
                let bin = Bin::new(name, v);
                operations::put(&bin)
            }
            OP_INCR => {
                let name = require_bin(&bin_name, "Increment")?;
                let v = val.unwrap_or(Value::Int(1));
                let bin = Bin::new(name, v);
                operations::add(&bin)
            }
            OP_APPEND => {
                let name = require_bin(&bin_name, "Append")?;
                let v = val.unwrap_or(Value::String(String::new()));
                let bin = Bin::new(name, v);
                operations::append(&bin)
            }
            OP_PREPEND => {
                let name = require_bin(&bin_name, "Prepend")?;
                let v = val.unwrap_or(Value::String(String::new()));
                let bin = Bin::new(name, v);
                operations::prepend(&bin)
            }
            OP_TOUCH => operations::touch(),
            OP_DELETE => operations::delete(),

            // ── List CDT operations ──────────────────────────
            OP_LIST_APPEND => {
                let name = require_bin(&bin_name, "list_append")?;
                let policy = parse_list_policy(dict)?;
                let v = val.unwrap_or(Value::Nil);
                list_ops::append(&policy, &name, v)
            }
            OP_LIST_APPEND_ITEMS => {
                let name = require_bin(&bin_name, "list_append_items")?;
                let policy = parse_list_policy(dict)?;
                let v = val.unwrap_or(Value::Nil);
                list_ops::append_items(&policy, &name, values_from_list(&v))
            }
            OP_LIST_INSERT => {
                let name = require_bin(&bin_name, "list_insert")?;
                let policy = parse_list_policy(dict)?;
                let index = get_index(dict)?;
                let v = val.unwrap_or(Value::Nil);
                list_ops::insert(&policy, &name, index, v)
            }
            OP_LIST_INSERT_ITEMS => {
                let name = require_bin(&bin_name, "list_insert_items")?;
                let policy = parse_list_policy(dict)?;
                let index = get_index(dict)?;
                let v = val.unwrap_or(Value::Nil);
                list_ops::insert_items(&policy, &name, index, values_from_list(&v))
            }
            OP_LIST_POP => {
                let name = require_bin(&bin_name, "list_pop")?;
                let index = get_index(dict)?;
                list_ops::pop(&name, index)
            }
            OP_LIST_POP_RANGE => {
                let name = require_bin(&bin_name, "list_pop_range")?;
                let index = get_index(dict)?;
                let count = get_count(dict)?.unwrap_or(1);
                list_ops::pop_range(&name, index, count)
            }
            OP_LIST_REMOVE => {
                let name = require_bin(&bin_name, "list_remove")?;
                let index = get_index(dict)?;
                list_ops::remove(&name, index)
            }
            OP_LIST_REMOVE_RANGE => {
                let name = require_bin(&bin_name, "list_remove_range")?;
                let index = get_index(dict)?;
                let count = get_count(dict)?.unwrap_or(1);
                list_ops::remove_range(&name, index, count)
            }
            OP_LIST_SET => {
                let name = require_bin(&bin_name, "list_set")?;
                let index = get_index(dict)?;
                let v = val.unwrap_or(Value::Nil);
                list_ops::set(&name, index, v)
            }
            OP_LIST_TRIM => {
                let name = require_bin(&bin_name, "list_trim")?;
                let index = get_index(dict)?;
                let count = get_count(dict)?.unwrap_or(0);
                list_ops::trim(&name, index, count)
            }
            OP_LIST_CLEAR => {
                let name = require_bin(&bin_name, "list_clear")?;
                list_ops::clear(&name)
            }
            OP_LIST_SIZE => {
                let name = require_bin(&bin_name, "list_size")?;
                list_ops::size(&name)
            }
            OP_LIST_GET => {
                let name = require_bin(&bin_name, "list_get")?;
                let index = get_index(dict)?;
                list_ops::get(&name, index)
            }
            OP_LIST_GET_RANGE => {
                let name = require_bin(&bin_name, "list_get_range")?;
                let index = get_index(dict)?;
                let count = get_count(dict)?.unwrap_or(1);
                list_ops::get_range(&name, index, count)
            }
            OP_LIST_GET_BY_VALUE => {
                let name = require_bin(&bin_name, "list_get_by_value")?;
                let v = val.unwrap_or(Value::Nil);
                let rt = int_to_list_return_type(get_return_type(dict)?);
                list_ops::get_by_value(&name, v, rt)
            }
            OP_LIST_GET_BY_INDEX => {
                let name = require_bin(&bin_name, "list_get_by_index")?;
                let index = get_index(dict)?;
                let rt = int_to_list_return_type(get_return_type(dict)?);
                list_ops::get_by_index(&name, index, rt)
            }
            OP_LIST_GET_BY_INDEX_RANGE => {
                let name = require_bin(&bin_name, "list_get_by_index_range")?;
                let index = get_index(dict)?;
                let rt = int_to_list_return_type(get_return_type(dict)?);
                match get_count(dict)? {
                    Some(count) => list_ops::get_by_index_range_count(&name, index, count, rt),
                    None => list_ops::get_by_index_range(&name, index, rt),
                }
            }
            OP_LIST_GET_BY_RANK => {
                let name = require_bin(&bin_name, "list_get_by_rank")?;
                let rank = get_rank(dict)?;
                let rt = int_to_list_return_type(get_return_type(dict)?);
                list_ops::get_by_rank(&name, rank, rt)
            }
            OP_LIST_GET_BY_RANK_RANGE => {
                let name = require_bin(&bin_name, "list_get_by_rank_range")?;
                let rank = get_rank(dict)?;
                let rt = int_to_list_return_type(get_return_type(dict)?);
                match get_count(dict)? {
                    Some(count) => list_ops::get_by_rank_range_count(&name, rank, count, rt),
                    None => list_ops::get_by_rank_range(&name, rank, rt),
                }
            }
            OP_LIST_GET_BY_VALUE_LIST => {
                let name = require_bin(&bin_name, "list_get_by_value_list")?;
                let v = val.unwrap_or(Value::Nil);
                let rt = int_to_list_return_type(get_return_type(dict)?);
                list_ops::get_by_value_list(&name, values_from_list(&v), rt)
            }
            OP_LIST_GET_BY_VALUE_RANGE => {
                let name = require_bin(&bin_name, "list_get_by_value_range")?;
                let begin = val.unwrap_or(Value::Nil);
                let end = get_val_end(dict)?;
                let rt = int_to_list_return_type(get_return_type(dict)?);
                list_ops::get_by_value_range(&name, begin, end, rt)
            }
            OP_LIST_REMOVE_BY_VALUE => {
                let name = require_bin(&bin_name, "list_remove_by_value")?;
                let v = val.unwrap_or(Value::Nil);
                let rt = int_to_list_return_type(get_return_type(dict)?);
                list_ops::remove_by_value(&name, v, rt)
            }
            OP_LIST_REMOVE_BY_VALUE_LIST => {
                let name = require_bin(&bin_name, "list_remove_by_value_list")?;
                let v = val.unwrap_or(Value::Nil);
                let rt = int_to_list_return_type(get_return_type(dict)?);
                list_ops::remove_by_value_list(&name, values_from_list(&v), rt)
            }
            OP_LIST_REMOVE_BY_VALUE_RANGE => {
                let name = require_bin(&bin_name, "list_remove_by_value_range")?;
                let begin = val.unwrap_or(Value::Nil);
                let end = get_val_end(dict)?;
                let rt = int_to_list_return_type(get_return_type(dict)?);
                list_ops::remove_by_value_range(&name, rt, begin, end)
            }
            OP_LIST_REMOVE_BY_INDEX => {
                let name = require_bin(&bin_name, "list_remove_by_index")?;
                let index = get_index(dict)?;
                let rt = int_to_list_return_type(get_return_type(dict)?);
                list_ops::remove_by_index(&name, index, rt)
            }
            OP_LIST_REMOVE_BY_INDEX_RANGE => {
                let name = require_bin(&bin_name, "list_remove_by_index_range")?;
                let index = get_index(dict)?;
                let rt = int_to_list_return_type(get_return_type(dict)?);
                match get_count(dict)? {
                    Some(count) => list_ops::remove_by_index_range_count(&name, index, count, rt),
                    None => list_ops::remove_by_index_range(&name, index, rt),
                }
            }
            OP_LIST_REMOVE_BY_RANK => {
                let name = require_bin(&bin_name, "list_remove_by_rank")?;
                let rank = get_rank(dict)?;
                let rt = int_to_list_return_type(get_return_type(dict)?);
                list_ops::remove_by_rank(&name, rank, rt)
            }
            OP_LIST_REMOVE_BY_RANK_RANGE => {
                let name = require_bin(&bin_name, "list_remove_by_rank_range")?;
                let rank = get_rank(dict)?;
                let rt = int_to_list_return_type(get_return_type(dict)?);
                match get_count(dict)? {
                    Some(count) => list_ops::remove_by_rank_range_count(&name, rank, count, rt),
                    None => list_ops::remove_by_rank_range(&name, rank, rt),
                }
            }
            OP_LIST_INCREMENT => {
                let name = require_bin(&bin_name, "list_increment")?;
                let policy = parse_list_policy(dict)?;
                let index = get_index(dict)?;
                let v: i64 = match &val {
                    Some(Value::Int(i)) => *i,
                    _ => 1,
                };
                list_ops::increment(&policy, &name, index, v)
            }
            OP_LIST_SORT => {
                let name = require_bin(&bin_name, "list_sort")?;
                let flags = parse_i32_flag(&val, "list_sort", "val")?;
                let sort_flags = match flags {
                    2 => ListSortFlags::DropDuplicates,
                    _ => ListSortFlags::Default,
                };
                list_ops::sort(&name, sort_flags)
            }
            OP_LIST_SET_ORDER => {
                let name = require_bin(&bin_name, "list_set_order")?;
                let order = parse_i32_flag(&val, "list_set_order", "val")?;
                let order_type = match order {
                    1 => ListOrderType::Ordered,
                    _ => ListOrderType::Unordered,
                };
                list_ops::set_order(&name, order_type, vec![])
            }

            // ── Map CDT operations ───────────────────────────
            OP_MAP_SET_ORDER => {
                let name = require_bin(&bin_name, "map_set_order")?;
                let order = parse_i32_flag(&val, "map_set_order", "val")?;
                let map_order = match order {
                    1 => MapOrder::KeyOrdered,
                    3 => MapOrder::KeyValueOrdered,
                    _ => MapOrder::Unordered,
                };
                map_ops::set_order(&name, map_order)
            }
            OP_MAP_PUT => {
                let name = require_bin(&bin_name, "map_put")?;
                let policy = parse_map_policy(dict)?;
                let key = get_map_key(dict)?;
                let v = val.unwrap_or(Value::Nil);
                map_ops::put(&policy, &name, key, v)
            }
            OP_MAP_PUT_ITEMS => {
                let name = require_bin(&bin_name, "map_put_items")?;
                let policy = parse_map_policy(dict)?;
                let v = val.unwrap_or(Value::Nil);
                // Convert Value::HashMap to HashMap
                match v {
                    Value::HashMap(map) => map_ops::put_items(&policy, &name, map),
                    _ => {
                        return Err(pyo3::exceptions::PyValueError::new_err(
                            "map_put_items requires a dict value",
                        ))
                    }
                }
            }
            OP_MAP_INCREMENT => {
                let name = require_bin(&bin_name, "map_increment")?;
                let policy = parse_map_policy(dict)?;
                let key = get_map_key(dict)?;
                let v = val.unwrap_or(Value::Int(1));
                map_ops::increment_value(&policy, &name, key, v)
            }
            OP_MAP_DECREMENT => {
                let name = require_bin(&bin_name, "map_decrement")?;
                let policy = parse_map_policy(dict)?;
                let key = get_map_key(dict)?;
                let v = val.unwrap_or(Value::Int(1));
                map_ops::decrement_value(&policy, &name, key, v)
            }
            OP_MAP_CLEAR => {
                let name = require_bin(&bin_name, "map_clear")?;
                map_ops::clear(&name)
            }
            OP_MAP_REMOVE_BY_KEY => {
                let name = require_bin(&bin_name, "map_remove_by_key")?;
                let key = get_map_key(dict)?;
                let rt = int_to_map_return_type(get_return_type(dict)?);
                map_ops::remove_by_key(&name, key, rt)
            }
            OP_MAP_REMOVE_BY_KEY_LIST => {
                let name = require_bin(&bin_name, "map_remove_by_key_list")?;
                let v = val.unwrap_or(Value::Nil);
                let rt = int_to_map_return_type(get_return_type(dict)?);
                map_ops::remove_by_key_list(&name, values_from_list(&v), rt)
            }
            OP_MAP_REMOVE_BY_KEY_RANGE => {
                let name = require_bin(&bin_name, "map_remove_by_key_range")?;
                let begin = val.unwrap_or(Value::Nil);
                let end = get_val_end(dict)?;
                let rt = int_to_map_return_type(get_return_type(dict)?);
                map_ops::remove_by_key_range(&name, begin, end, rt)
            }
            OP_MAP_REMOVE_BY_VALUE => {
                let name = require_bin(&bin_name, "map_remove_by_value")?;
                let v = val.unwrap_or(Value::Nil);
                let rt = int_to_map_return_type(get_return_type(dict)?);
                map_ops::remove_by_value(&name, v, rt)
            }
            OP_MAP_REMOVE_BY_VALUE_LIST => {
                let name = require_bin(&bin_name, "map_remove_by_value_list")?;
                let v = val.unwrap_or(Value::Nil);
                let rt = int_to_map_return_type(get_return_type(dict)?);
                map_ops::remove_by_value_list(&name, values_from_list(&v), rt)
            }
            OP_MAP_REMOVE_BY_VALUE_RANGE => {
                let name = require_bin(&bin_name, "map_remove_by_value_range")?;
                let begin = val.unwrap_or(Value::Nil);
                let end = get_val_end(dict)?;
                let rt = int_to_map_return_type(get_return_type(dict)?);
                map_ops::remove_by_value_range(&name, begin, end, rt)
            }
            OP_MAP_REMOVE_BY_INDEX => {
                let name = require_bin(&bin_name, "map_remove_by_index")?;
                let index = get_index(dict)?;
                let rt = int_to_map_return_type(get_return_type(dict)?);
                map_ops::remove_by_index(&name, index, rt)
            }
            OP_MAP_REMOVE_BY_INDEX_RANGE => {
                let name = require_bin(&bin_name, "map_remove_by_index_range")?;
                let index = get_index(dict)?;
                let rt = int_to_map_return_type(get_return_type(dict)?);
                let count = get_count(dict)?.unwrap_or(1);
                map_ops::remove_by_index_range(&name, index, count, rt)
            }
            OP_MAP_REMOVE_BY_RANK => {
                let name = require_bin(&bin_name, "map_remove_by_rank")?;
                let rank = get_rank(dict)?;
                let rt = int_to_map_return_type(get_return_type(dict)?);
                map_ops::remove_by_rank(&name, rank, rt)
            }
            OP_MAP_REMOVE_BY_RANK_RANGE => {
                let name = require_bin(&bin_name, "map_remove_by_rank_range")?;
                let rank = get_rank(dict)?;
                let rt = int_to_map_return_type(get_return_type(dict)?);
                let count = get_count(dict)?.unwrap_or(1);
                map_ops::remove_by_rank_range(&name, rank, count, rt)
            }
            OP_MAP_SIZE => {
                let name = require_bin(&bin_name, "map_size")?;
                map_ops::size(&name)
            }
            OP_MAP_GET_BY_KEY => {
                let name = require_bin(&bin_name, "map_get_by_key")?;
                let key = get_map_key(dict)?;
                let rt = int_to_map_return_type(get_return_type(dict)?);
                map_ops::get_by_key(&name, key, rt)
            }
            OP_MAP_GET_BY_KEY_RANGE => {
                let name = require_bin(&bin_name, "map_get_by_key_range")?;
                let begin = val.unwrap_or(Value::Nil);
                let end = get_val_end(dict)?;
                let rt = int_to_map_return_type(get_return_type(dict)?);
                map_ops::get_by_key_range(&name, begin, end, rt)
            }
            OP_MAP_GET_BY_VALUE => {
                let name = require_bin(&bin_name, "map_get_by_value")?;
                let v = val.unwrap_or(Value::Nil);
                let rt = int_to_map_return_type(get_return_type(dict)?);
                map_ops::get_by_value(&name, v, rt)
            }
            OP_MAP_GET_BY_VALUE_RANGE => {
                let name = require_bin(&bin_name, "map_get_by_value_range")?;
                let begin = val.unwrap_or(Value::Nil);
                let end = get_val_end(dict)?;
                let rt = int_to_map_return_type(get_return_type(dict)?);
                map_ops::get_by_value_range(&name, begin, end, rt)
            }
            OP_MAP_GET_BY_INDEX => {
                let name = require_bin(&bin_name, "map_get_by_index")?;
                let index = get_index(dict)?;
                let rt = int_to_map_return_type(get_return_type(dict)?);
                map_ops::get_by_index(&name, index, rt)
            }
            OP_MAP_GET_BY_INDEX_RANGE => {
                let name = require_bin(&bin_name, "map_get_by_index_range")?;
                let index = get_index(dict)?;
                let rt = int_to_map_return_type(get_return_type(dict)?);
                let count = get_count(dict)?.unwrap_or(1);
                map_ops::get_by_index_range(&name, index, count, rt)
            }
            OP_MAP_GET_BY_RANK => {
                let name = require_bin(&bin_name, "map_get_by_rank")?;
                let rank = get_rank(dict)?;
                let rt = int_to_map_return_type(get_return_type(dict)?);
                map_ops::get_by_rank(&name, rank, rt)
            }
            OP_MAP_GET_BY_RANK_RANGE => {
                let name = require_bin(&bin_name, "map_get_by_rank_range")?;
                let rank = get_rank(dict)?;
                let rt = int_to_map_return_type(get_return_type(dict)?);
                let count = get_count(dict)?.unwrap_or(1);
                map_ops::get_by_rank_range(&name, rank, count, rt)
            }
            OP_MAP_GET_BY_KEY_LIST => {
                let name = require_bin(&bin_name, "map_get_by_key_list")?;
                let v = val.unwrap_or(Value::Nil);
                let rt = int_to_map_return_type(get_return_type(dict)?);
                map_ops::get_by_key_list(&name, values_from_list(&v), rt)
            }
            OP_MAP_GET_BY_VALUE_LIST => {
                let name = require_bin(&bin_name, "map_get_by_value_list")?;
                let v = val.unwrap_or(Value::Nil);
                let rt = int_to_map_return_type(get_return_type(dict)?);
                map_ops::get_by_value_list(&name, values_from_list(&v), rt)
            }

            // ── HLL CDT operations ───────────────────────────
            OP_HLL_INIT => {
                let name = require_bin(&bin_name, "hll_init")?;
                let policy = parse_hll_policy(dict)?;
                let index_bit_count: i64 = dict
                    .get_item("index_bit_count")?
                    .ok_or_else(|| {
                        pyo3::exceptions::PyValueError::new_err(
                            "hll_init requires 'index_bit_count'",
                        )
                    })?
                    .extract()?;
                let minhash_bit_count: i64 = dict
                    .get_item("minhash_bit_count")?
                    .map(|v| v.extract())
                    .transpose()?
                    .unwrap_or(-1);
                hll_ops::init_with_min_hash(&policy, &name, index_bit_count, minhash_bit_count)
            }
            OP_HLL_ADD => {
                let name = require_bin(&bin_name, "hll_add")?;
                let policy = parse_hll_policy(dict)?;
                let v = val.unwrap_or(Value::Nil);
                let list = values_from_list(&v);
                let index_bit_count: i64 = dict
                    .get_item("index_bit_count")?
                    .map(|v| v.extract())
                    .transpose()?
                    .unwrap_or(-1);
                let minhash_bit_count: i64 = dict
                    .get_item("minhash_bit_count")?
                    .map(|v| v.extract())
                    .transpose()?
                    .unwrap_or(-1);
                hll_ops::add_with_index_and_min_hash(
                    &policy,
                    &name,
                    list,
                    index_bit_count,
                    minhash_bit_count,
                )
            }
            OP_HLL_GET_COUNT => {
                let name = require_bin(&bin_name, "hll_get_count")?;
                hll_ops::get_count(&name)
            }
            OP_HLL_GET_UNION => {
                let name = require_bin(&bin_name, "hll_get_union")?;
                let v = val.unwrap_or(Value::Nil);
                hll_ops::get_union(&name, values_from_list(&v))
            }
            OP_HLL_GET_UNION_COUNT => {
                let name = require_bin(&bin_name, "hll_get_union_count")?;
                let v = val.unwrap_or(Value::Nil);
                hll_ops::get_union_count(&name, values_from_list(&v))
            }
            OP_HLL_GET_INTERSECT_COUNT => {
                let name = require_bin(&bin_name, "hll_get_intersect_count")?;
                let v = val.unwrap_or(Value::Nil);
                hll_ops::get_intersect_count(&name, values_from_list(&v))
            }
            OP_HLL_GET_SIMILARITY => {
                let name = require_bin(&bin_name, "hll_get_similarity")?;
                let v = val.unwrap_or(Value::Nil);
                hll_ops::get_similarity(&name, values_from_list(&v))
            }
            OP_HLL_DESCRIBE => {
                let name = require_bin(&bin_name, "hll_describe")?;
                hll_ops::describe(&name)
            }
            OP_HLL_FOLD => {
                let name = require_bin(&bin_name, "hll_fold")?;
                let index_bit_count: i64 = dict
                    .get_item("index_bit_count")?
                    .ok_or_else(|| {
                        pyo3::exceptions::PyValueError::new_err(
                            "hll_fold requires 'index_bit_count'",
                        )
                    })?
                    .extract()?;
                hll_ops::fold(&name, index_bit_count)
            }
            OP_HLL_SET_UNION => {
                let name = require_bin(&bin_name, "hll_set_union")?;
                let policy = parse_hll_policy(dict)?;
                let v = val.unwrap_or(Value::Nil);
                hll_ops::set_union(&policy, &name, values_from_list(&v))
            }

            // ── Bitwise CDT operations ─────────────────────────
            OP_BIT_RESIZE => {
                let name = require_bin(&bin_name, "bit_resize")?;
                let byte_size = get_byte_size(dict)?;
                let resize_flags = get_resize_flags(dict)?;
                let policy = parse_bit_policy(dict)?;
                bit_ops::resize(&name, byte_size, resize_flags, &policy)
            }
            OP_BIT_INSERT => {
                let name = require_bin(&bin_name, "bit_insert")?;
                let byte_offset = get_byte_offset(dict)?;
                let v = val.unwrap_or(Value::Nil);
                let policy = parse_bit_policy(dict)?;
                bit_ops::insert(&name, byte_offset, v, &policy)
            }
            OP_BIT_REMOVE => {
                let name = require_bin(&bin_name, "bit_remove")?;
                let byte_offset = get_byte_offset(dict)?;
                let byte_size = get_byte_size(dict)?;
                let policy = parse_bit_policy(dict)?;
                bit_ops::remove(&name, byte_offset, byte_size, &policy)
            }
            OP_BIT_SET => {
                let name = require_bin(&bin_name, "bit_set")?;
                let bit_offset = get_bit_offset(dict)?;
                let bit_size = get_bit_size(dict)?;
                let v = val.unwrap_or(Value::Nil);
                let policy = parse_bit_policy(dict)?;
                bit_ops::set(&name, bit_offset, bit_size, v, &policy)
            }
            OP_BIT_OR => {
                let name = require_bin(&bin_name, "bit_or")?;
                let bit_offset = get_bit_offset(dict)?;
                let bit_size = get_bit_size(dict)?;
                let v = val.unwrap_or(Value::Nil);
                let policy = parse_bit_policy(dict)?;
                bit_ops::or(&name, bit_offset, bit_size, v, &policy)
            }
            OP_BIT_XOR => {
                let name = require_bin(&bin_name, "bit_xor")?;
                let bit_offset = get_bit_offset(dict)?;
                let bit_size = get_bit_size(dict)?;
                let v = val.unwrap_or(Value::Nil);
                let policy = parse_bit_policy(dict)?;
                bit_ops::xor(&name, bit_offset, bit_size, v, &policy)
            }
            OP_BIT_AND => {
                let name = require_bin(&bin_name, "bit_and")?;
                let bit_offset = get_bit_offset(dict)?;
                let bit_size = get_bit_size(dict)?;
                let v = val.unwrap_or(Value::Nil);
                let policy = parse_bit_policy(dict)?;
                bit_ops::and(&name, bit_offset, bit_size, v, &policy)
            }
            OP_BIT_NOT => {
                let name = require_bin(&bin_name, "bit_not")?;
                let bit_offset = get_bit_offset(dict)?;
                let bit_size = get_bit_size(dict)?;
                let policy = parse_bit_policy(dict)?;
                bit_ops::not(&name, bit_offset, bit_size, &policy)
            }
            OP_BIT_LSHIFT => {
                let name = require_bin(&bin_name, "bit_lshift")?;
                let bit_offset = get_bit_offset(dict)?;
                let bit_size = get_bit_size(dict)?;
                let shift = get_shift(dict)?;
                let policy = parse_bit_policy(dict)?;
                bit_ops::lshift(&name, bit_offset, bit_size, shift, &policy)
            }
            OP_BIT_RSHIFT => {
                let name = require_bin(&bin_name, "bit_rshift")?;
                let bit_offset = get_bit_offset(dict)?;
                let bit_size = get_bit_size(dict)?;
                let shift = get_shift(dict)?;
                let policy = parse_bit_policy(dict)?;
                bit_ops::rshift(&name, bit_offset, bit_size, shift, &policy)
            }
            OP_BIT_ADD => {
                let name = require_bin(&bin_name, "bit_add")?;
                let bit_offset = get_bit_offset(dict)?;
                let bit_size = get_bit_size(dict)?;
                let value_int: i64 = match &val {
                    Some(Value::Int(i)) => *i,
                    Some(other) => {
                        return Err(pyo3::exceptions::PyValueError::new_err(format!(
                            "bit operation requires an integer value, got {:?}",
                            other
                        )))
                    }
                    None => {
                        return Err(pyo3::exceptions::PyValueError::new_err(
                            "bit operation requires a 'val' parameter",
                        ))
                    }
                };
                let signed = get_signed(dict)?;
                let action = get_overflow_action(dict)?;
                let policy = parse_bit_policy(dict)?;
                bit_ops::add(
                    &name, bit_offset, bit_size, value_int, signed, action, &policy,
                )
            }
            OP_BIT_SUBTRACT => {
                let name = require_bin(&bin_name, "bit_subtract")?;
                let bit_offset = get_bit_offset(dict)?;
                let bit_size = get_bit_size(dict)?;
                let value_int: i64 = match &val {
                    Some(Value::Int(i)) => *i,
                    Some(other) => {
                        return Err(pyo3::exceptions::PyValueError::new_err(format!(
                            "bit operation requires an integer value, got {:?}",
                            other
                        )))
                    }
                    None => {
                        return Err(pyo3::exceptions::PyValueError::new_err(
                            "bit operation requires a 'val' parameter",
                        ))
                    }
                };
                let signed = get_signed(dict)?;
                let action = get_overflow_action(dict)?;
                let policy = parse_bit_policy(dict)?;
                bit_ops::subtract(
                    &name, bit_offset, bit_size, value_int, signed, action, &policy,
                )
            }
            OP_BIT_SET_INT => {
                let name = require_bin(&bin_name, "bit_set_int")?;
                let bit_offset = get_bit_offset(dict)?;
                let bit_size = get_bit_size(dict)?;
                let value_int: i64 = match &val {
                    Some(Value::Int(i)) => *i,
                    Some(other) => {
                        return Err(pyo3::exceptions::PyValueError::new_err(format!(
                            "bit operation requires an integer value, got {:?}",
                            other
                        )))
                    }
                    None => {
                        return Err(pyo3::exceptions::PyValueError::new_err(
                            "bit operation requires a 'val' parameter",
                        ))
                    }
                };
                let policy = parse_bit_policy(dict)?;
                bit_ops::set_int(&name, bit_offset, bit_size, value_int, &policy)
            }
            OP_BIT_GET => {
                let name = require_bin(&bin_name, "bit_get")?;
                let bit_offset = get_bit_offset(dict)?;
                let bit_size = get_bit_size(dict)?;
                bit_ops::get(&name, bit_offset, bit_size)
            }
            OP_BIT_COUNT => {
                let name = require_bin(&bin_name, "bit_count")?;
                let bit_offset = get_bit_offset(dict)?;
                let bit_size = get_bit_size(dict)?;
                bit_ops::count(&name, bit_offset, bit_size)
            }
            OP_BIT_LSCAN => {
                let name = require_bin(&bin_name, "bit_lscan")?;
                let bit_offset = get_bit_offset(dict)?;
                let bit_size = get_bit_size(dict)?;
                let scan_val = get_scan_value(dict)?;
                bit_ops::lscan(&name, bit_offset, bit_size, scan_val)
            }
            OP_BIT_RSCAN => {
                let name = require_bin(&bin_name, "bit_rscan")?;
                let bit_offset = get_bit_offset(dict)?;
                let bit_size = get_bit_size(dict)?;
                let scan_val = get_scan_value(dict)?;
                bit_ops::rscan(&name, bit_offset, bit_size, scan_val)
            }
            OP_BIT_GET_INT => {
                let name = require_bin(&bin_name, "bit_get_int")?;
                let bit_offset = get_bit_offset(dict)?;
                let bit_size = get_bit_size(dict)?;
                let signed = get_signed(dict)?;
                bit_ops::get_int(&name, bit_offset, bit_size, signed)
            }

            _ => {
                return Err(pyo3::exceptions::PyValueError::new_err(format!(
                    "Unsupported operation code: {op_code}. Supported codes: \
                     READ={OP_READ}, WRITE={OP_WRITE}, INCR={OP_INCR}, \
                     APPEND={OP_APPEND}, PREPEND={OP_PREPEND}, TOUCH={OP_TOUCH}, DELETE={OP_DELETE}, \
                     List CDT=1001-1031, Map CDT=2001-2027, HLL CDT=3001-3010, Bit CDT=4001-4054"
                )));
            }
        };

        rust_ops.push(op);
    }

    Ok(rust_ops)
}

#[cfg(test)]
mod tests {
    use super::parse_i32_flag;
    use aerospike_core::Value;
    use pyo3::{exceptions::PyTypeError, exceptions::PyValueError, PyErr, Python};

    #[test]
    fn parse_i32_flag_defaults_to_zero_for_missing_or_nil() {
        assert_eq!(
            parse_i32_flag(&None, "list_sort", "val").expect("None should default to 0"),
            0
        );
        assert_eq!(
            parse_i32_flag(&Some(Value::Nil), "list_sort", "val").expect("Nil should default to 0"),
            0
        );
    }

    #[test]
    fn parse_i32_flag_accepts_in_range_int() {
        let parsed = parse_i32_flag(&Some(Value::Int(i64::from(i32::MAX))), "list_sort", "val")
            .expect("i32 max should be accepted");
        assert_eq!(parsed, i32::MAX);
    }

    #[test]
    fn parse_i32_flag_rejects_out_of_range_int() {
        Python::initialize();
        let err: PyErr = parse_i32_flag(
            &Some(Value::Int(i64::from(i32::MAX) + 1)),
            "list_sort",
            "val",
        )
        .expect_err("out-of-range int should fail");
        Python::attach(|py| {
            assert!(err.is_instance_of::<PyValueError>(py));
        });
    }

    #[test]
    fn parse_i32_flag_rejects_non_int() {
        let err: PyErr = parse_i32_flag(&Some(Value::String("2".to_string())), "list_sort", "val")
            .expect_err("non-int should fail");
        Python::initialize();
        Python::attach(|py| {
            assert!(err.is_instance_of::<PyTypeError>(py));
        });
    }
}
