use std::fmt::{Display, Formatter};

/// SQL-specific operators for SQL expression generation.
/// These represent the actual SQL syntax, as opposed to Hamelin's syntax.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Operator {
    // Arithmetic
    Minus,
    Plus,
    Asterisk,
    Slash,
    Percent,

    // String
    Concat, // ||

    // Comparison
    Eq,  // =
    Neq, // <>
    Lt,  // <
    Lte, // <=
    Gt,  // >
    Gte, // >=

    // Logical
    Not,
    And,
    Or,

    // Membership
    In,
    NotIn,

    // Null checking
    Is,
    IsNot,

    // Pattern matching
    Like,
}

impl Operator {
    pub fn of(name: &str) -> anyhow::Result<Self> {
        match name {
            // Arithmetic
            "-" => Ok(Self::Minus),
            "+" => Ok(Self::Plus),
            "*" => Ok(Self::Asterisk),
            "/" => Ok(Self::Slash),
            "%" => Ok(Self::Percent),

            // String
            "||" => Ok(Self::Concat),

            // Comparison
            "=" => Ok(Self::Eq),
            "<>" => Ok(Self::Neq),
            "<" => Ok(Self::Lt),
            "<=" => Ok(Self::Lte),
            ">" => Ok(Self::Gt),
            ">=" => Ok(Self::Gte),

            // Logical
            "NOT" => Ok(Self::Not),
            "AND" => Ok(Self::And),
            "OR" => Ok(Self::Or),

            // Membership
            "IN" => Ok(Self::In),
            "NOT IN" => Ok(Self::NotIn),

            // Null checking
            "IS" => Ok(Self::Is),
            "IS NOT" => Ok(Self::IsNot),

            // Pattern matching
            "LIKE" => Ok(Self::Like),

            _ => anyhow::bail!("Unknown SQL operator: {}", name),
        }
    }
}

impl Display for Operator {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let s = match self {
            Self::Minus => "-",
            Self::Plus => "+",
            Self::Asterisk => "*",
            Self::Slash => "/",
            Self::Percent => "%",
            Self::Concat => "||",
            Self::Eq => "=",
            Self::Neq => "<>",
            Self::Lt => "<",
            Self::Lte => "<=",
            Self::Gt => ">",
            Self::Gte => ">=",
            Self::Not => "NOT",
            Self::And => "AND",
            Self::Or => "OR",
            Self::In => "IN",
            Self::NotIn => "NOT IN",
            Self::Is => "IS",
            Self::IsNot => "IS NOT",
            Self::Like => "LIKE",
        };
        write!(f, "{}", s)
    }
}

/// Conversion from Hamelin operator to SQL operator.
/// Not all Hamelin operators have SQL equivalents (e.g., Range `..` has no SQL equivalent).
impl TryFrom<crate::operator::Operator> for Operator {
    type Error = ConversionError;

    fn try_from(op: crate::operator::Operator) -> Result<Self, Self::Error> {
        use crate::operator::Operator;
        match op {
            Operator::Minus => Ok(Self::Minus),
            Operator::Plus => Ok(Self::Plus),
            Operator::Asterisk => Ok(Self::Asterisk),
            Operator::Slash => Ok(Self::Slash),
            Operator::Percent => Ok(Self::Percent),
            Operator::Concat => Ok(Self::Concat),
            Operator::Eq => Ok(Self::Eq),
            Operator::Neq => Ok(Self::Neq),
            Operator::Lt => Ok(Self::Lt),
            Operator::Lte => Ok(Self::Lte),
            Operator::Gt => Ok(Self::Gt),
            Operator::Gte => Ok(Self::Gte),
            Operator::Not => Ok(Self::Not),
            Operator::And => Ok(Self::And),
            Operator::Or => Ok(Self::Or),
            Operator::In => Ok(Self::In),
            Operator::NotIn => Ok(Self::NotIn),
            Operator::Is => Ok(Self::Is),
            Operator::IsNot => Ok(Self::IsNot),
            Operator::Like => Ok(Self::Like),
            Operator::Range => Err(ConversionError::NoSqlEquivalent("Range (..)".to_string())),
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConversionError {
    NoSqlEquivalent(String),
}

impl Display for ConversionError {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::NoSqlEquivalent(op) => write!(f, "Operator {} has no SQL equivalent", op),
        }
    }
}

impl std::error::Error for ConversionError {}
