use crate::ast::Expr;
use crate::catalog::Catalog;
use crate::error::DbError;
use crate::storage::Storage;
use crate::types::Value;

use super::common::{
    eval_expr_to_value, index_by_id, index_by_name, table_by_object_id, table_has_primary_key,
    value_to_object_id,
};
use crate::executor::clock::Clock;
use crate::executor::context::ExecutionContext;
use crate::executor::model::ContextTable;

pub(crate) fn eval_index_col(
    args: &[Expr],
    row: &[ContextTable],
    ctx: &mut ExecutionContext,
    catalog: &dyn Catalog,
    storage: &dyn Storage,
    clock: &dyn Clock,
) -> Result<Value, DbError> {
    if args.len() != 3 {
        return Err(DbError::Execution("INDEX_COL expects 3 arguments".into()));
    }
    let object_val = eval_expr_to_value(&args[0], row, ctx, catalog, storage, clock)?;
    let index_val = eval_expr_to_value(&args[1], row, ctx, catalog, storage, clock)?;
    let key_val = eval_expr_to_value(&args[2], row, ctx, catalog, storage, clock)?;
    if object_val.is_null() || index_val.is_null() || key_val.is_null() {
        return Ok(Value::Null);
    }
    let Some(object_id) = value_to_object_id(&object_val, catalog, None) else {
        return Ok(Value::Null);
    };
    let Some(table) = table_by_object_id(catalog, object_id) else {
        return Ok(Value::Null);
    };
    let Some(key_ordinal) = key_val.to_integer_i64() else {
        return Ok(Value::Null);
    };
    let index = if let Some(index_id) = index_val.to_integer_i64() {
        index_by_id(catalog, object_id, index_id as i32)
    } else {
        index_by_name(catalog, object_id, &index_val.to_string_value())
    };
    let Some(index) = index else {
        return Ok(Value::Null);
    };
    if key_ordinal <= 0 || (key_ordinal as usize) > index.column_ids.len() {
        return Ok(Value::Null);
    }
    let col_id = index.column_ids[(key_ordinal - 1) as usize];
    let Some(column) = table.columns.iter().find(|c| c.id == col_id) else {
        return Ok(Value::Null);
    };
    Ok(Value::NVarChar(column.name.clone()))
}

pub(crate) fn eval_indexkey_property(
    args: &[Expr],
    row: &[ContextTable],
    ctx: &mut ExecutionContext,
    catalog: &dyn Catalog,
    storage: &dyn Storage,
    clock: &dyn Clock,
) -> Result<Value, DbError> {
    if args.len() != 4 {
        return Err(DbError::Execution(
            "INDEXKEY_PROPERTY expects 4 arguments".into(),
        ));
    }
    let object_val = eval_expr_to_value(&args[0], row, ctx, catalog, storage, clock)?;
    let index_val = eval_expr_to_value(&args[1], row, ctx, catalog, storage, clock)?;
    let key_val = eval_expr_to_value(&args[2], row, ctx, catalog, storage, clock)?;
    let prop_val = eval_expr_to_value(&args[3], row, ctx, catalog, storage, clock)?;
    if object_val.is_null() || index_val.is_null() || key_val.is_null() || prop_val.is_null() {
        return Ok(Value::Null);
    }
    let Some(object_id) = value_to_object_id(&object_val, catalog, None) else {
        return Ok(Value::Null);
    };
    let Some(key_ordinal) = key_val.to_integer_i64() else {
        return Ok(Value::Null);
    };
    let index = if let Some(index_id) = index_val.to_integer_i64() {
        index_by_id(catalog, object_id, index_id as i32)
    } else {
        index_by_name(catalog, object_id, &index_val.to_string_value())
    };
    let Some(index) = index else {
        return Ok(Value::Null);
    };
    if key_ordinal <= 0 || (key_ordinal as usize) > index.column_ids.len() {
        return Ok(Value::Null);
    }
    let col_id = index.column_ids[(key_ordinal - 1) as usize] as i32;
    let prop = prop_val.to_string_value().to_ascii_uppercase();
    Ok(match prop.as_str() {
        "COLUMNID" => Value::Int(col_id),
        "ISDESCENDING" => Value::Int(0),
        "KEYORDINAL" => Value::Int(key_ordinal as i32),
        _ => Value::Null,
    })
}

pub(crate) fn eval_indexproperty(
    args: &[Expr],
    row: &[ContextTable],
    ctx: &mut ExecutionContext,
    catalog: &dyn Catalog,
    storage: &dyn Storage,
    clock: &dyn Clock,
) -> Result<Value, DbError> {
    if args.len() != 3 {
        return Err(DbError::Execution(
            "INDEXPROPERTY expects 3 arguments".into(),
        ));
    }
    let object_val = eval_expr_to_value(&args[0], row, ctx, catalog, storage, clock)?;
    let index_val = eval_expr_to_value(&args[1], row, ctx, catalog, storage, clock)?;
    let prop_val = eval_expr_to_value(&args[2], row, ctx, catalog, storage, clock)?;
    if object_val.is_null() || index_val.is_null() || prop_val.is_null() {
        return Ok(Value::Null);
    }
    let Some(object_id) = value_to_object_id(&object_val, catalog, None) else {
        return Ok(Value::Null);
    };
    let Some(table) = table_by_object_id(catalog, object_id) else {
        return Ok(Value::Null);
    };
    let index = if let Some(index_id) = index_val.to_integer_i64() {
        index_by_id(catalog, object_id, index_id as i32)
    } else {
        index_by_name(catalog, object_id, &index_val.to_string_value())
    };
    let Some(index) = index else {
        return Ok(Value::Null);
    };
    let prop = prop_val.to_string_value().to_ascii_uppercase();
    Ok(match prop.as_str() {
        "INDEXID" => Value::Int(index.id as i32),
        "ISCLUSTERED" => Value::Int(if index.is_clustered { 1 } else { 0 }),
        "ISUNIQUE" => Value::Int(if index.is_unique { 1 } else { 0 }),
        "ISPRIMARYKEY" => Value::Int(if table_has_primary_key(table) { 1 } else { 0 }),
        "ISDISABLED" => Value::Int(0),
        "INDEXDEPTH" => Value::Int(1),
        "INDEXFILLFACTOR" => Value::Int(0),
        _ => Value::Null,
    })
}
