use std::cmp::Ordering;

use crate::ast::{BinaryOp, UnaryOp};
use crate::error::DbError;
use crate::types::Value;

use super::value_helpers::{is_string_type, rescale_raw, to_decimal_parts, to_i64, value_to_f64};
use super::value_ops::{compare_values, truthy};

pub(crate) fn eval_binary(
    op: &BinaryOp,
    lv: Value,
    rv: Value,
    ansi_nulls: bool,
    concat_null_yields_null: bool,
    arithabort: bool,
    ansi_warnings: bool,
) -> Result<Value, DbError> {
    match op {
        BinaryOp::Eq => Ok(compare_bool(lv, rv, |o| o == Ordering::Equal, ansi_nulls)),
        BinaryOp::NotEq => Ok(compare_bool(lv, rv, |o| o != Ordering::Equal, ansi_nulls)),
        BinaryOp::Gt => Ok(compare_bool(lv, rv, |o| o == Ordering::Greater, ansi_nulls)),
        BinaryOp::Lt => Ok(compare_bool(lv, rv, |o| o == Ordering::Less, ansi_nulls)),
        BinaryOp::Gte => Ok(compare_bool(
            lv,
            rv,
            |o| matches!(o, Ordering::Greater | Ordering::Equal),
            ansi_nulls,
        )),
        BinaryOp::Lte => Ok(compare_bool(
            lv,
            rv,
            |o| matches!(o, Ordering::Less | Ordering::Equal),
            ansi_nulls,
        )),
        BinaryOp::And => eval_and(lv, rv),
        BinaryOp::Or => eval_or(lv, rv),
        BinaryOp::Add => eval_add(lv, rv, concat_null_yields_null, arithabort, ansi_warnings),
        BinaryOp::Subtract => eval_subtract(lv, rv, arithabort, ansi_warnings),
        BinaryOp::Multiply => eval_multiply(lv, rv, arithabort, ansi_warnings),
        BinaryOp::Divide => eval_divide(lv, rv, arithabort, ansi_warnings),
        BinaryOp::Modulo => eval_modulo(lv, rv, arithabort, ansi_warnings),
        BinaryOp::BitwiseAnd => eval_bitwise_and(lv, rv),
        BinaryOp::BitwiseOr => eval_bitwise_or(lv, rv),
        BinaryOp::BitwiseXor => eval_bitwise_xor(lv, rv),
    }
}

pub(crate) fn eval_unary(op: &UnaryOp, val: Value) -> Result<Value, DbError> {
    if val.is_null() {
        return Ok(Value::Null);
    }
    match op {
        UnaryOp::Negate => match val {
            Value::TinyInt(v) => Ok(Value::SmallInt(-(v as i16))),
            Value::SmallInt(v) => Ok(Value::SmallInt(-v)),
            Value::Int(v) => Ok(Value::Int(-v)),
            Value::BigInt(v) => Ok(Value::BigInt(-v)),
            Value::Float(v) => Ok(Value::Float((-f64::from_bits(v)).to_bits())),
            Value::Decimal(raw, scale) => Ok(Value::Decimal(-raw, scale)),
            Value::Money(v) => Ok(Value::Money(-v)),
            Value::SmallMoney(v) => Ok(Value::SmallMoney(-v)),
            _ => Err(DbError::Execution(format!(
                "cannot negate value of type {:?}",
                val.data_type()
            ))),
        },
        UnaryOp::Not => Ok(Value::Bit(!truthy(&val))),
        UnaryOp::BitwiseNot => eval_bitwise_not(val),
    }
}

fn eval_add(
    lv: Value,
    rv: Value,
    concat_null_yields_null: bool,
    arithabort: bool,
    ansi_warnings: bool,
) -> Result<Value, DbError> {
    if is_string_type(&lv) || is_string_type(&rv) {
        if (lv.is_null() || rv.is_null()) && concat_null_yields_null {
            return Ok(Value::Null);
        }
        let ls = if lv.is_null() {
            String::new()
        } else {
            lv.to_string_value()
        };
        let rs = if rv.is_null() {
            String::new()
        } else {
            rv.to_string_value()
        };
        return Ok(Value::VarChar(format!("{}{}", ls, rs)));
    }
    if lv.is_null() || rv.is_null() {
        return Ok(Value::Null);
    }
    if is_float_type(&lv) || is_float_type(&rv) {
        let a = value_to_f64(&lv)?;
        let b = value_to_f64(&rv)?;
        return Ok(Value::Float((a + b).to_bits()));
    }
    if is_money_type(&lv) || is_money_type(&rv) {
        let a = extract_money_as_i128(&lv);
        let b = extract_money_as_i128(&rv);
        return Ok(Value::Money(a + b));
    }
    match (&lv, &rv) {
        (Value::Decimal(_, _), _) | (_, Value::Decimal(_, _)) => {
            let (ar, as_) = to_decimal_parts(&lv);
            let (br, bs) = to_decimal_parts(&rv);
            let max_scale = as_.max(bs);
            let a = rescale_raw(ar, as_, max_scale);
            let b = rescale_raw(br, bs, max_scale);
            Ok(Value::Decimal(a + b, max_scale))
        }
        _ => {
            let a = to_i64(&lv)?;
            let b = to_i64(&rv)?;
            match a.checked_add(b) {
                Some(v) => Ok(Value::BigInt(v)),
                None => arithmetic_overflow("addition", arithabort, ansi_warnings),
            }
        }
    }
}

fn eval_subtract(
    lv: Value,
    rv: Value,
    arithabort: bool,
    ansi_warnings: bool,
) -> Result<Value, DbError> {
    if lv.is_null() || rv.is_null() {
        return Ok(Value::Null);
    }
    if is_float_type(&lv) || is_float_type(&rv) {
        let a = value_to_f64(&lv)?;
        let b = value_to_f64(&rv)?;
        return Ok(Value::Float((a - b).to_bits()));
    }
    if is_money_type(&lv) || is_money_type(&rv) {
        let a = extract_money_as_i128(&lv);
        let b = extract_money_as_i128(&rv);
        return Ok(Value::Money(a - b));
    }
    match (&lv, &rv) {
        (Value::Decimal(_, _), _) | (_, Value::Decimal(_, _)) => {
            let (ar, as_) = to_decimal_parts(&lv);
            let (br, bs) = to_decimal_parts(&rv);
            let max_scale = as_.max(bs);
            let a = rescale_raw(ar, as_, max_scale);
            let b = rescale_raw(br, bs, max_scale);
            Ok(Value::Decimal(a - b, max_scale))
        }
        _ => {
            let a = to_i64(&lv)?;
            let b = to_i64(&rv)?;
            match a.checked_sub(b) {
                Some(v) => Ok(Value::BigInt(v)),
                None => arithmetic_overflow("subtraction", arithabort, ansi_warnings),
            }
        }
    }
}

fn eval_multiply(
    lv: Value,
    rv: Value,
    arithabort: bool,
    ansi_warnings: bool,
) -> Result<Value, DbError> {
    if lv.is_null() || rv.is_null() {
        return Ok(Value::Null);
    }
    if is_float_type(&lv) || is_float_type(&rv) {
        let a = value_to_f64(&lv)?;
        let b = value_to_f64(&rv)?;
        return Ok(Value::Float((a * b).to_bits()));
    }
    if is_money_type(&lv) || is_money_type(&rv) {
        let a = extract_money_as_i128(&lv);
        let b = extract_money_as_i128(&rv);
        return Ok(Value::Money((a * b) / 10000));
    }
    match (&lv, &rv) {
        (Value::Decimal(_, _), _) | (_, Value::Decimal(_, _)) => {
            let (ar, as_) = to_decimal_parts(&lv);
            let (br, bs) = to_decimal_parts(&rv);
            let result_scale = as_ + bs;
            Ok(Value::Decimal(ar * br, result_scale))
        }
        _ => {
            let a = to_i64(&lv)?;
            let b = to_i64(&rv)?;
            match a.checked_mul(b) {
                Some(v) => Ok(Value::BigInt(v)),
                None => arithmetic_overflow("multiplication", arithabort, ansi_warnings),
            }
        }
    }
}

fn eval_divide(
    lv: Value,
    rv: Value,
    arithabort: bool,
    ansi_warnings: bool,
) -> Result<Value, DbError> {
    if lv.is_null() || rv.is_null() {
        return Ok(Value::Null);
    }
    if is_float_type(&lv) || is_float_type(&rv) {
        let a = value_to_f64(&lv)?;
        let b = value_to_f64(&rv)?;
        if b == 0.0 {
            return divide_by_zero(arithabort, ansi_warnings);
        }
        return Ok(Value::Float((a / b).to_bits()));
    }
    if is_money_type(&lv) || is_money_type(&rv) {
        let a = extract_money_as_i128(&lv);
        let b = extract_money_as_i128(&rv);
        if b == 0 {
            return divide_by_zero(arithabort, ansi_warnings);
        }
        return Ok(Value::Money(a * 10000 / b));
    }
    match (&lv, &rv) {
        (Value::Decimal(_, _), _) | (_, Value::Decimal(_, _)) => {
            let (ar, as_) = to_decimal_parts(&lv);
            let (br, bs) = to_decimal_parts(&rv);
            if br == 0 {
                return divide_by_zero(arithabort, ansi_warnings);
            }
            let scale = 6u8.max(as_);
            let numerator = rescale_raw(ar, as_, scale + bs);
            Ok(Value::Decimal(numerator / br, scale))
        }
        _ => {
            let a = to_i64(&lv)?;
            let b = to_i64(&rv)?;
            if b == 0 {
                return divide_by_zero(arithabort, ansi_warnings);
            }
            Ok(Value::BigInt(a / b))
        }
    }
}

fn eval_modulo(
    lv: Value,
    rv: Value,
    arithabort: bool,
    ansi_warnings: bool,
) -> Result<Value, DbError> {
    if lv.is_null() || rv.is_null() {
        return Ok(Value::Null);
    }
    let a = to_i64(&lv)?;
    let b = to_i64(&rv)?;
    if b == 0 {
        return divide_by_zero(arithabort, ansi_warnings);
    }
    Ok(Value::BigInt(a % b))
}

fn eval_and(lv: Value, rv: Value) -> Result<Value, DbError> {
    match (&lv, &rv) {
        (Value::Null, Value::Null) => Ok(Value::Null),
        (Value::Null, Value::Bit(false)) => Ok(Value::Bit(false)),
        (Value::Bit(false), Value::Null) => Ok(Value::Bit(false)),
        (Value::Null, _) => Ok(Value::Null),
        (_, Value::Null) => Ok(Value::Null),
        _ => Ok(Value::Bit(truthy(&lv) && truthy(&rv))),
    }
}

fn eval_or(lv: Value, rv: Value) -> Result<Value, DbError> {
    match (&lv, &rv) {
        (Value::Null, Value::Null) => Ok(Value::Null),
        (Value::Null, Value::Bit(true)) => Ok(Value::Bit(true)),
        (Value::Bit(true), Value::Null) => Ok(Value::Bit(true)),
        (Value::Null, _) => Ok(Value::Null),
        (_, Value::Null) => Ok(Value::Null),
        _ => Ok(Value::Bit(truthy(&lv) || truthy(&rv))),
    }
}

pub(crate) fn compare_bool<F>(lv: Value, rv: Value, pred: F, ansi_nulls: bool) -> Value
where
    F: FnOnce(Ordering) -> bool,
{
    if lv.is_null() || rv.is_null() {
        if ansi_nulls {
            return Value::Null;
        } else if lv.is_null() && rv.is_null() {
            return Value::Bit(pred(Ordering::Equal));
        } else {
            return Value::Bit(false);
        }
    }
    Value::Bit(pred(compare_values(&lv, &rv)))
}

fn is_float_type(v: &Value) -> bool {
    matches!(v, Value::Float(_))
}

fn is_money_type(v: &Value) -> bool {
    matches!(v, Value::Money(_) | Value::SmallMoney(_))
}

fn extract_money_as_i128(v: &Value) -> i128 {
    match v {
        Value::Money(r) => *r,
        Value::SmallMoney(r) => *r as i128,
        Value::Int(v) => *v as i128 * 10000,
        Value::BigInt(v) => *v as i128 * 10000,
        Value::TinyInt(v) => *v as i128 * 10000,
        Value::SmallInt(v) => *v as i128 * 10000,
        Value::Decimal(raw, scale) => super::value_helpers::rescale_raw(*raw, *scale, 4),
        Value::Float(v) => (f64::from_bits(*v) * 10000.0) as i128,
        _ => 0,
    }
}

fn eval_bitwise_and(lv: Value, rv: Value) -> Result<Value, DbError> {
    if lv.is_null() || rv.is_null() {
        return Ok(Value::Null);
    }
    let a = to_i64(&lv)?;
    let b = to_i64(&rv)?;
    Ok(Value::BigInt(a & b))
}

fn eval_bitwise_or(lv: Value, rv: Value) -> Result<Value, DbError> {
    if lv.is_null() || rv.is_null() {
        return Ok(Value::Null);
    }
    let a = to_i64(&lv)?;
    let b = to_i64(&rv)?;
    Ok(Value::BigInt(a | b))
}

fn eval_bitwise_xor(lv: Value, rv: Value) -> Result<Value, DbError> {
    if lv.is_null() || rv.is_null() {
        return Ok(Value::Null);
    }
    let a = to_i64(&lv)?;
    let b = to_i64(&rv)?;
    Ok(Value::BigInt(a ^ b))
}

fn arithmetic_overflow(
    what: &str,
    arithabort: bool,
    ansi_warnings: bool,
) -> Result<Value, DbError> {
    if arithabort || ansi_warnings {
        Err(DbError::Execution(format!(
            "Arithmetic overflow error during {}",
            what
        )))
    } else {
        Ok(Value::Null)
    }
}

fn divide_by_zero(arithabort: bool, ansi_warnings: bool) -> Result<Value, DbError> {
    if arithabort || ansi_warnings {
        Err(DbError::divide_by_zero())
    } else {
        Ok(Value::Null)
    }
}

fn eval_bitwise_not(val: Value) -> Result<Value, DbError> {
    if val.is_null() {
        return Ok(Value::Null);
    }
    let a = to_i64(&val)?;
    Ok(Value::BigInt(!a))
}
