//! Eval implementations for arithmetic operators

use anyhow::bail;

use hamelin_lib::func::defs::{
    NumericDivide, NumericMinus, NumericModulo, NumericMultiply, NumericPlus,
};

use crate::null_propagate;
use crate::registry::EvalRegistry;
use crate::reverse_eval::domain::Constraint;
use crate::value::DecimalValue;
use crate::value::Value;

/// Register all arithmetic operator eval implementations.
pub fn register(registry: &mut EvalRegistry) {
    // NumericPlus: left + right
    registry.register_eval::<NumericPlus>(|mut params| {
        let left = null_propagate!(params.take()?);
        let right = null_propagate!(params.take()?);

        match (left, right) {
            (Value::Int(l), Value::Int(r)) => Ok(Value::Int(l + r)),
            (Value::Double(l), Value::Double(r)) => Ok(Value::Double(l + r)),
            (Value::Int(l), Value::Double(r)) => Ok(Value::Double(l as f64 + r)),
            (Value::Double(l), Value::Int(r)) => Ok(Value::Double(l + r as f64)),
            (Value::Decimal(l), Value::Decimal(r)) => Ok(Value::Decimal(l.add(&r))),
            (Value::Decimal(l), Value::Int(r)) => Ok(Value::Decimal(l.add(&r.into()))),
            (Value::Int(l), Value::Decimal(r)) => Ok(Value::Decimal(DecimalValue::from(l).add(&r))),
            (Value::Decimal(l), Value::Double(r)) => Ok(Value::Double(f64::from(l) + r)),
            (Value::Double(l), Value::Decimal(r)) => Ok(Value::Double(l + f64::from(r))),
            _ => bail!("Invalid operands for + operator"),
        }
    });

    registry.register_reverse::<NumericPlus>(|output_constraint, params| {
        // a + const = output => a = output - const
        let constant_val = params.take_first_defined()?;

        output_constraint.map(|output_val| match (output_val, &constant_val) {
            (Value::Int(out), Value::Int(c)) => Ok(Value::Int(out - c)),
            (Value::Double(out), Value::Double(c)) => Ok(Value::Double(out - c)),
            (Value::Int(out), Value::Double(c)) => Ok(Value::Double(*out as f64 - c)),
            (Value::Double(out), Value::Int(c)) => Ok(Value::Double(out - *c as f64)),
            (Value::Decimal(out), Value::Decimal(c)) => Ok(Value::Decimal(out.sub(c))),
            (Value::Decimal(out), Value::Int(c)) => Ok(Value::Decimal(out.sub(&(*c).into()))),
            (Value::Int(out), Value::Decimal(c)) => {
                Ok(Value::Decimal(DecimalValue::from(*out).sub(c)))
            }
            (Value::Decimal(out), Value::Double(c)) => Ok(Value::Double(f64::from(out) - c)),
            (Value::Double(out), Value::Decimal(c)) => Ok(Value::Double(out - f64::from(c))),
            _ => bail!("Invalid operands for reverse addition"),
        })
    });

    // NumericMinus: left - right
    registry.register_eval::<NumericMinus>(|mut params| {
        let left = null_propagate!(params.take()?);
        let right = null_propagate!(params.take()?);

        match (left, right) {
            (Value::Int(l), Value::Int(r)) => Ok(Value::Int(l - r)),
            (Value::Double(l), Value::Double(r)) => Ok(Value::Double(l - r)),
            (Value::Int(l), Value::Double(r)) => Ok(Value::Double(l as f64 - r)),
            (Value::Double(l), Value::Int(r)) => Ok(Value::Double(l - r as f64)),
            (Value::Decimal(l), Value::Decimal(r)) => Ok(Value::Decimal(l.sub(&r))),
            (Value::Decimal(l), Value::Int(r)) => Ok(Value::Decimal(l.sub(&r.into()))),
            (Value::Int(l), Value::Decimal(r)) => Ok(Value::Decimal(DecimalValue::from(l).sub(&r))),
            (Value::Decimal(l), Value::Double(r)) => Ok(Value::Double(f64::from(l) - r)),
            (Value::Double(l), Value::Decimal(r)) => Ok(Value::Double(l - f64::from(r))),
            _ => bail!("Invalid operands for - operator"),
        }
    });

    registry.register_reverse::<NumericMinus>(|output_constraint, params| {
        // Check which parameter is constant
        let left = params.get_by_name("left").ok().and_then(|opt| opt.as_ref());
        let right = params
            .get_by_name("right")
            .ok()
            .and_then(|opt| opt.as_ref());

        match (left, right) {
            (None, Some(right_val)) => {
                // left has variable, right is constant
                // a - const = output => a = output + const
                output_constraint.map(|output_val| match (output_val, right_val) {
                    (Value::Int(out), Value::Int(r)) => Ok(Value::Int(out + r)),
                    (Value::Double(out), Value::Double(r)) => Ok(Value::Double(out + r)),
                    (Value::Int(out), Value::Double(r)) => Ok(Value::Double(*out as f64 + r)),
                    (Value::Double(out), Value::Int(r)) => Ok(Value::Double(out + *r as f64)),
                    (Value::Decimal(out), Value::Decimal(r)) => Ok(Value::Decimal(out.add(&r))),
                    (Value::Decimal(out), Value::Int(r)) => {
                        Ok(Value::Decimal(out.add(&(*r).into())))
                    }
                    (Value::Int(out), Value::Decimal(r)) => {
                        Ok(Value::Decimal(DecimalValue::from(*out).add(r)))
                    }
                    (Value::Decimal(out), Value::Double(r)) => {
                        Ok(Value::Double(f64::from(out) + r))
                    }
                    (Value::Double(out), Value::Decimal(r)) => {
                        Ok(Value::Double(out + f64::from(r)))
                    }
                    _ => bail!("Invalid operands for reverse subtraction"),
                })
            }
            (Some(left_val), None) => {
                // left is constant, right has variable
                // const - b = output => b = const - output
                output_constraint.map(|output_val| match (left_val, output_val) {
                    (Value::Int(l), Value::Int(out)) => Ok(Value::Int(l - out)),
                    (Value::Double(l), Value::Double(out)) => Ok(Value::Double(l - out)),
                    (Value::Int(l), Value::Double(out)) => Ok(Value::Double(*l as f64 - out)),
                    (Value::Double(l), Value::Int(out)) => Ok(Value::Double(l - *out as f64)),
                    (Value::Decimal(l), Value::Decimal(out)) => Ok(Value::Decimal(l.sub(&out))),
                    (Value::Decimal(l), Value::Int(out)) => {
                        Ok(Value::Decimal(l.sub(&(*out).into())))
                    }
                    (Value::Int(l), Value::Decimal(out)) => {
                        Ok(Value::Decimal(DecimalValue::from(*l).sub(out)))
                    }
                    (Value::Decimal(l), Value::Double(out)) => {
                        Ok(Value::Double(f64::from(l) - out))
                    }
                    (Value::Double(l), Value::Decimal(out)) => {
                        Ok(Value::Double(l - f64::from(out)))
                    }
                    _ => bail!("Invalid operands for reverse subtraction"),
                })
            }
            _ => bail!("Invalid reverse evaluation: expected exactly one variable"),
        }
    });

    // NumericMultiply: left * right
    registry.register_eval::<NumericMultiply>(|mut params| {
        let left = null_propagate!(params.take()?);
        let right = null_propagate!(params.take()?);

        match (left, right) {
            (Value::Int(l), Value::Int(r)) => Ok(Value::Int(l * r)),
            (Value::Double(l), Value::Double(r)) => Ok(Value::Double(l * r)),
            (Value::Int(l), Value::Double(r)) => Ok(Value::Double(l as f64 * r)),
            (Value::Double(l), Value::Int(r)) => Ok(Value::Double(l * r as f64)),
            (Value::Decimal(l), Value::Decimal(r)) => Ok(Value::Decimal(l.mul(&r))),
            (Value::Decimal(l), Value::Int(r)) => Ok(Value::Decimal(l.mul(&r.into()))),
            (Value::Int(l), Value::Decimal(r)) => Ok(Value::Decimal(DecimalValue::from(l).mul(&r))),
            (Value::Decimal(l), Value::Double(r)) => Ok(Value::Double(f64::from(l) * r)),
            (Value::Double(l), Value::Decimal(r)) => Ok(Value::Double(l * f64::from(r))),
            _ => bail!("Invalid operands for * operator"),
        }
    });

    registry.register_reverse::<NumericMultiply>(|output_constraint, params| {
        // a * const = output => a = output / const
        let constant_val = params.take_first_defined()?;

        output_constraint.map(|output_val| match (output_val, &constant_val) {
            (Value::Int(out), Value::Int(c)) => {
                if *c == 0 {
                    bail!("Division by zero in reverse multiplication")
                }
                Ok(Value::Int(out / c))
            }
            (Value::Double(out), Value::Double(c)) => {
                if *c == 0.0 {
                    bail!("Division by zero in reverse multiplication")
                }
                Ok(Value::Double(out / c))
            }
            (Value::Int(out), Value::Double(c)) => {
                if *c == 0.0 {
                    bail!("Division by zero in reverse multiplication")
                }
                Ok(Value::Double(*out as f64 / c))
            }
            (Value::Double(out), Value::Int(c)) => {
                if *c == 0 {
                    bail!("Division by zero in reverse multiplication")
                }
                Ok(Value::Double(out / *c as f64))
            }
            (Value::Decimal(out), Value::Decimal(c)) => Ok(Value::Decimal(out.div(&c)?)),
            (Value::Decimal(out), Value::Int(c)) => {
                if *c == 0 {
                    bail!("Division by zero in reverse multiplication")
                }
                Ok(Value::Decimal(out.div(&(*c).into())?))
            }
            (Value::Int(out), Value::Decimal(c)) => {
                Ok(Value::Decimal(DecimalValue::from(*out).div(&c)?))
            }
            (Value::Decimal(out), Value::Double(c)) => {
                if *c == 0.0 {
                    bail!("Division by zero in reverse multiplication")
                }
                Ok(Value::Double(f64::from(out) / c))
            }
            (Value::Double(out), Value::Decimal(c)) => {
                let c_double: f64 = f64::from(c);
                if c_double == 0.0 {
                    bail!("Division by zero in reverse multiplication")
                }
                Ok(Value::Double(out / c_double))
            }
            _ => bail!("Invalid operands for reverse multiplication"),
        })
    });

    // NumericDivide: left / right
    registry.register_eval::<NumericDivide>(|mut params| {
        let left = null_propagate!(params.take()?);
        let right = null_propagate!(params.take()?);

        match (left, right) {
            (Value::Int(l), Value::Int(r)) => {
                if r == 0 {
                    bail!("Division by zero")
                } else {
                    Ok(Value::Int(l / r))
                }
            }
            (Value::Double(l), Value::Double(r)) => {
                if r == 0.0 {
                    bail!("Division by zero")
                } else {
                    Ok(Value::Double(l / r))
                }
            }
            (Value::Int(l), Value::Double(r)) => {
                if r == 0.0 {
                    bail!("Division by zero")
                } else {
                    Ok(Value::Double(l as f64 / r))
                }
            }
            (Value::Double(l), Value::Int(r)) => {
                if r == 0 {
                    bail!("Division by zero")
                } else {
                    Ok(Value::Double(l / r as f64))
                }
            }
            (Value::Decimal(l), Value::Decimal(r)) => Ok(Value::Decimal(l.div(&r)?)),
            (Value::Decimal(l), Value::Int(r)) => {
                if r == 0 {
                    bail!("Division by zero")
                }
                Ok(Value::Decimal(l.div(&r.into())?))
            }
            (Value::Int(l), Value::Decimal(r)) => {
                Ok(Value::Decimal(DecimalValue::from(l).div(&r)?))
            }
            (Value::Decimal(l), Value::Double(r)) => {
                if r == 0.0 {
                    bail!("Division by zero")
                }
                Ok(Value::Double(f64::from(l) / r))
            }
            (Value::Double(l), Value::Decimal(r)) => {
                let r_double: f64 = f64::from(r);
                if r_double == 0.0 {
                    bail!("Division by zero")
                }
                Ok(Value::Double(l / r_double))
            }
            _ => bail!("Invalid operands for / operator"),
        }
    });

    registry.register_reverse::<NumericDivide>(|output_constraint, params| {
        // Check which parameter is constant
        let left = params.get_by_name("left").ok().and_then(|opt| opt.as_ref());
        let right = params
            .get_by_name("right")
            .ok()
            .and_then(|opt| opt.as_ref());

        match (left, right) {
            (None, Some(right_val)) => {
                // left has variable, right is constant
                // a / const = output => a = output * const

                // Check if divisor is zero
                let is_zero = match right_val {
                    Value::Int(r) => *r == 0,
                    Value::Double(r) => *r == 0.0,
                    Value::Decimal(r) => r.unscaled == 0,
                    _ => false,
                };

                // Division by zero is undefined, so there's no valid value
                if is_zero {
                    return Ok(Constraint::Empty);
                }

                output_constraint.map(|output_val| match (output_val, right_val) {
                    (Value::Int(out), Value::Int(r)) => Ok(Value::Int(out * r)),
                    (Value::Double(out), Value::Double(r)) => Ok(Value::Double(out * r)),
                    (Value::Int(out), Value::Double(r)) => Ok(Value::Double(*out as f64 * r)),
                    (Value::Double(out), Value::Int(r)) => Ok(Value::Double(out * *r as f64)),
                    (Value::Decimal(out), Value::Decimal(r)) => Ok(Value::Decimal(out.mul(&r))),
                    (Value::Decimal(out), Value::Int(r)) => {
                        Ok(Value::Decimal(out.mul(&(*r).into())))
                    }
                    (Value::Int(out), Value::Decimal(r)) => {
                        Ok(Value::Decimal(DecimalValue::from(*out).mul(r)))
                    }
                    (Value::Decimal(out), Value::Double(r)) => {
                        Ok(Value::Double(f64::from(out) * r))
                    }
                    (Value::Double(out), Value::Decimal(r)) => {
                        Ok(Value::Double(out * f64::from(r)))
                    }
                    _ => bail!("Invalid operands for reverse division"),
                })
            }
            (Some(left_val), None) => {
                // left is constant, right has variable
                // const / b = output => b = const / output
                output_constraint.map(|output_val| match (left_val, output_val) {
                    (Value::Int(l), Value::Int(out)) => {
                        if *out == 0 {
                            bail!("Division by zero in reverse division")
                        }
                        Ok(Value::Int(l / out))
                    }
                    (Value::Double(l), Value::Double(out)) => {
                        if *out == 0.0 {
                            bail!("Division by zero in reverse division")
                        }
                        Ok(Value::Double(l / out))
                    }
                    (Value::Int(l), Value::Double(out)) => {
                        if *out == 0.0 {
                            bail!("Division by zero in reverse division")
                        }
                        Ok(Value::Double(*l as f64 / out))
                    }
                    (Value::Double(l), Value::Int(out)) => {
                        if *out == 0 {
                            bail!("Division by zero in reverse division")
                        }
                        Ok(Value::Double(l / *out as f64))
                    }
                    (Value::Decimal(l), Value::Decimal(out)) => Ok(Value::Decimal(l.div(&out)?)),
                    (Value::Decimal(l), Value::Int(out)) => {
                        if *out == 0 {
                            bail!("Division by zero in reverse division")
                        }
                        Ok(Value::Decimal(l.div(&(*out).into())?))
                    }
                    (Value::Int(l), Value::Decimal(out)) => {
                        Ok(Value::Decimal(DecimalValue::from(*l).div(&out)?))
                    }
                    (Value::Decimal(l), Value::Double(out)) => {
                        if *out == 0.0 {
                            bail!("Division by zero in reverse division")
                        }
                        Ok(Value::Double(f64::from(l) / out))
                    }
                    (Value::Double(l), Value::Decimal(out)) => {
                        let out_double: f64 = f64::from(out);
                        if out_double == 0.0 {
                            bail!("Division by zero in reverse division")
                        }
                        Ok(Value::Double(l / out_double))
                    }
                    _ => bail!("Invalid operands for reverse division"),
                })
            }
            _ => bail!("Invalid reverse evaluation: expected exactly one variable"),
        }
    });

    // NumericModulo: left % right (no reverse eval)
    registry.register_eval::<NumericModulo>(|mut params| {
        let left = null_propagate!(params.take()?);
        let right = null_propagate!(params.take()?);

        match (left, right) {
            (Value::Int(l), Value::Int(r)) => {
                if r == 0 {
                    bail!("Division by zero")
                } else {
                    Ok(Value::Int(l % r))
                }
            }
            (Value::Double(l), Value::Double(r)) => {
                if r == 0.0 {
                    bail!("Division by zero")
                } else {
                    Ok(Value::Double(l % r))
                }
            }
            (Value::Int(l), Value::Double(r)) => {
                if r == 0.0 {
                    bail!("Division by zero")
                } else {
                    Ok(Value::Double((l as f64) % r))
                }
            }
            (Value::Double(l), Value::Int(r)) => {
                if r == 0 {
                    bail!("Division by zero")
                } else {
                    Ok(Value::Double(l % (r as f64)))
                }
            }
            _ => bail!("Invalid operands for % operator"),
        }
    });

    // No reverse eval for modulo - it's not invertible
}
