use crate::data::Value;

pub enum Condition {
  Simple(String, Vec<Value>),
  And(Box<Condition>, Box<Condition>),
  Or(Box<Condition>, Box<Condition>),
  Group(Box<Condition>),
}

impl Condition {
  pub fn new(condition: &str) -> Self {
    Condition::Simple(condition.to_string(), vec![])
  }

  pub fn and(self, other: Self) -> Self {
    Condition::And(Box::new(self), Box::new(other))
  }

  pub fn or(self, other: Self) -> Self {
    Condition::Or(Box::new(self), Box::new(other))
  }

  pub fn group(self) -> Self {
    Condition::Group(Box::new(self))
  }

  pub fn is_equal(left: &str, right: impl Into<Value> + Clone) -> Self {
    let value = right.clone().into();
    match Self::from_optional_value(left, value) {
      Some(condition) => condition,
      None => Condition::Simple(format!("{} = ?", left), vec![right.into()]),
    }
  }

  pub fn not_equal(left: &str, right: impl Into<Value> + Clone) -> Self {
    let value = right.clone().into();
    match Self::from_optional_value(left, value) {
      Some(condition) => condition,
      None => Condition::Simple(format!("{} != ?", left), vec![right.into()]),
    }
  }

  pub fn is_null(value: &str) -> Self {
    Condition::Simple(format!("{} IS NULL", value), vec![])
  }

  pub fn not_null(value: &str) -> Self {
    Condition::Simple(format!("{} IS NOT NULL", value), vec![])
  }

  pub fn is_in(left: &str, right: Vec<Value>) -> Self {
    // Use helper function to handle special cases
    if let Some(condition) = Self::handle_empty_or_all_none(left, &right, true) {
      return condition;
    }

    let right_list = right
      .iter()
      .map(|_v| "'?'".to_string())
      .collect::<Vec<_>>()
      .join(", ");
    Condition::Simple(format!("{} IN ({})", left, right_list), right)
  }

  pub fn not_in(left: &str, right: Vec<Value>) -> Self {
    // Use helper function to handle special cases
    if let Some(condition) = Self::handle_empty_or_all_none(left, &right, true) {
      return condition;
    }

    let right_list = right
      .iter()
      .map(|_v| "'?'".to_string())
      .collect::<Vec<_>>()
      .join(", ");
    Condition::Simple(format!("{} NOT IN ({})", left, right_list), right)
  }

  pub fn like(left: &str, right: impl Into<Value> + Clone) -> Self {
    let value = right.clone().into();
    match Self::from_optional_value(left, value) {
      Some(condition) => condition,
      None => Condition::Simple(format!("{} LIKE '?'", left), vec![right.into()]),
    }
  }

  pub fn not_like(left: &str, right: impl Into<Value> + Clone) -> Self {
    let value = right.clone().into();
    match Self::from_optional_value(left, value) {
      Some(condition) => condition,
      None => Condition::Simple(format!("{} NOT LIKE '?'", left), vec![right.into()]),
    }
  }

  pub fn i_like(left: &str, right: impl Into<Value> + Clone) -> Self {
    let value = right.clone().into();
    match Self::from_optional_value(left, value) {
      Some(condition) => condition,
      None => Condition::Simple(format!("{} ILIKE '?'", left), vec![right.into()]),
    }
  }

  pub fn not_i_like(left: &str, right: impl Into<Value> + Clone) -> Self {
    let value = right.clone().into();
    match Self::from_optional_value(left, value) {
      Some(condition) => condition,
      None => Condition::Simple(format!("{} NOT ILIKE '?'", left), vec![right.into()]),
    }
  }

  pub fn gt(left: &str, right: impl Into<Value> + Clone) -> Self {
    let value = right.clone().into();
    match Self::from_optional_value(left, value) {
      Some(condition) => condition,
      None => Condition::Simple(format!("{} > ?", left), vec![right.into()]),
    }
  }

  pub fn gte(left: &str, right: impl Into<Value> + Clone) -> Self {
    let value = right.clone().into();
    match Self::from_optional_value(left, value) {
      Some(condition) => condition,
      None => Condition::Simple(format!("{} >= ?", left), vec![right.into()]),
    }
  }

  pub fn lt(left: &str, right: impl Into<Value> + Clone) -> Self {
    let value = right.clone().into();
    match Self::from_optional_value(left, value) {
      Some(condition) => condition,
      None => Condition::Simple(format!("{} < ?", left), vec![right.into()]),
    }
  }

  pub fn lte(left: &str, right: impl Into<Value> + Clone) -> Self {
    let value = right.clone().into();
    match Self::from_optional_value(left, value) {
      Some(condition) => condition,
      None => Condition::Simple(format!("{} <= ?", left), vec![right.into()]),
    }
  }

  // Private helper function to handle optional values
  fn from_optional_value(left: &str, value: Value) -> Option<Self> {
    match value {
      Value::OptionalInt(None) => Some(Condition::is_null(left)),
      Value::OptionalBigInt(None) => Some(Condition::is_null(left)),
      Value::OptionalFloat(None) => Some(Condition::is_null(left)),
      Value::OptionalDouble(None) => Some(Condition::is_null(left)),
      Value::OptionalBool(None) => Some(Condition::is_null(left)),
      Value::OptionalText(None) => Some(Condition::is_null(left)),
      Value::OptionalDateTime(None) => Some(Condition::is_null(left)),
      _ => None, // For non-optional or Some(value), let the primary method handle it
    }
  }

  // Private helper to handle `empty` or `all-None` lists
  fn handle_empty_or_all_none(left: &str, right: &[Value], negate: bool) -> Option<Self> {
    if right.is_empty() {
      // For an empty list, return an always-false condition
      // NOT IN with empty list is always TRUE, but we're defaulting to SQL-SAFE result (FALSE)
      return Some(Condition::Simple(
        if negate {
          "TRUE".to_string()
        } else {
          "FALSE".to_string()
        },
        vec![],
      ));
    }

    // Check if all elements in the `right` vector are `None` (Optional*)
    if right.iter().all(|v| {
      matches!(
        v,
        Value::OptionalInt(None)
          | Value::OptionalBigInt(None)
          | Value::OptionalFloat(None)
          | Value::OptionalDouble(None)
          | Value::OptionalBool(None)
          | Value::OptionalText(None)
          | Value::OptionalDateTime(None)
      )
    }) {
      // If all values are None, handle as NULL or NOT NULL
      return Some(if negate {
        Condition::not_null(left)
      } else {
        Condition::is_null(left)
      });
    }

    // Otherwise, this is not an empty or all-none case
    None
  }

  pub fn to_sql(&self, counter: &mut usize) -> (String, Vec<Value>) {
    let mut sql = String::new();
    let mut binds = Vec::new();

    match self {
      Condition::Simple(condition, values) => {
        // Replace each instance of '?' with increasing numbered binds
        let mut numbered_condition = String::new();
        let chars = condition.chars().peekable();

        for c in chars {
          if c == '?' {
            // Increment the counter and replace `?` with a numbered bind
            *counter += 1;
            numbered_condition.push_str(&format!("${}", *counter));
          } else {
            numbered_condition.push(c);
          }
        }

        sql.push_str(&numbered_condition);
        binds.extend(values.clone());
      }
      Condition::And(left, right) => {
        let (left_sql, left_binds) = left.to_sql(counter);
        let (right_sql, right_binds) = right.to_sql(counter);
        sql.push_str(&format!("{} AND {}", left_sql, right_sql));
        binds.extend(left_binds);
        binds.extend(right_binds);
      }
      Condition::Or(left, right) => {
        let (left_sql, left_binds) = left.to_sql(counter);
        let (right_sql, right_binds) = right.to_sql(counter);
        sql.push_str(&format!("{} OR {}", left_sql, right_sql));
        binds.extend(left_binds);
        binds.extend(right_binds);
      }
      Condition::Group(inner) => {
        let (inner_sql, inner_binds) = inner.to_sql(counter);
        sql.push_str(&format!("({})", inner_sql));
        binds.extend(inner_binds);
      }
    };

    (sql, binds)
  }
}
