use crate::data::Value;
use sqlx::{FromRow, Postgres};

#[allow(async_fn_in_trait)]
pub trait ExecutableQuery {
  fn build(&self) -> (String, Vec<Value>);

  async fn execute(&self) -> Result<sqlx::postgres::PgQueryResult, sqlx::Error> {
    // Build the SQL query and its values
    let (query_string, values) = self.build();

    // Start constructing the query
    let mut query = sqlx::query(&query_string);

    // Bind each value to its respective placeholder
    for value in values {
      match value {
        Value::Int(n) => query = query.bind(n),
        Value::OptionalInt(n) => query = query.bind(n),
        Value::BigInt(n) => query = query.bind(n),
        Value::OptionalBigInt(n) => query = query.bind(n),
        Value::Float(n) => query = query.bind(n),
        Value::OptionalFloat(n) => query = query.bind(n),
        Value::Double(n) => query = query.bind(n),
        Value::OptionalDouble(n) => query = query.bind(n),
        Value::Bool(n) => query = query.bind(n),
        Value::OptionalBool(n) => query = query.bind(n),
        Value::Text(n) => query = query.bind(n),
        Value::OptionalText(n) => query = query.bind(n),
        Value::DateTime(n) => query = query.bind(n),
        Value::OptionalDateTime(n) => query = query.bind(n),
      }
    }

    let pool = crate::data::pool();
    query.execute(pool).await
  }

  async fn fetch_optional<
    T: Send + Unpin + for<'r> FromRow<'r, <Postgres as sqlx::Database>::Row>,
  >(
    &self,
  ) -> Option<T> {
    let (query_string, values) = self.build();
    let mut query_as = sqlx::query_as(&query_string);
    for value in values {
      match value {
        Value::Int(n) => query_as = query_as.bind(n),
        Value::OptionalInt(n) => query_as = query_as.bind(n),
        Value::BigInt(n) => query_as = query_as.bind(n),
        Value::OptionalBigInt(n) => query_as = query_as.bind(n),
        Value::Float(n) => query_as = query_as.bind(n),
        Value::OptionalFloat(n) => query_as = query_as.bind(n),
        Value::Double(n) => query_as = query_as.bind(n),
        Value::OptionalDouble(n) => query_as = query_as.bind(n),
        Value::Bool(n) => query_as = query_as.bind(n),
        Value::OptionalBool(n) => query_as = query_as.bind(n),
        Value::Text(n) => query_as = query_as.bind(n),
        Value::OptionalText(n) => query_as = query_as.bind(n),
        Value::DateTime(n) => query_as = query_as.bind(n),
        Value::OptionalDateTime(n) => query_as = query_as.bind(n),
      }
    }

    let pool = crate::data::pool();
    query_as.fetch_optional(pool).await.unwrap_or_else(|err| {
      log::error!(
        "Unable to fetch optional on query '{}': {}",
        query_string,
        err
      );
      None
    })
  }

  async fn fetch_all<T: Send + Unpin + for<'r> FromRow<'r, <Postgres as sqlx::Database>::Row>>(
    &self,
  ) -> Vec<T> {
    let (query_string, values) = self.build();
    let mut query_as = sqlx::query_as(&query_string);
    for value in values {
      match value {
        Value::Int(n) => query_as = query_as.bind(n),
        Value::OptionalInt(n) => query_as = query_as.bind(n),
        Value::BigInt(n) => query_as = query_as.bind(n),
        Value::OptionalBigInt(n) => query_as = query_as.bind(n),
        Value::Float(n) => query_as = query_as.bind(n),
        Value::OptionalFloat(n) => query_as = query_as.bind(n),
        Value::Double(n) => query_as = query_as.bind(n),
        Value::OptionalDouble(n) => query_as = query_as.bind(n),
        Value::Bool(n) => query_as = query_as.bind(n),
        Value::OptionalBool(n) => query_as = query_as.bind(n),
        Value::Text(n) => query_as = query_as.bind(n),
        Value::OptionalText(n) => query_as = query_as.bind(n),
        Value::DateTime(n) => query_as = query_as.bind(n),
        Value::OptionalDateTime(n) => query_as = query_as.bind(n),
      }
    }

    let pool = crate::data::pool();
    query_as.fetch_all(pool).await.unwrap_or_else(|err| {
      log::error!("Unable to fetch all on query '{}': {}", query_string, err);
      vec![]
    })
  }
}
