use crate::error::Result;
use chrono::{DateTime, Utc};
use redis::{Client as RedisClient, RedisResult, aio::MultiplexedConnection as RedisConnection};
use sqlx::{Pool, Postgres, postgres::PgPoolOptions};
use std::{fmt, fmt::Display, sync::OnceLock, time::Duration};

pub mod condition;
pub mod events;
mod executable_query;
pub mod guilds;
pub mod insert;
pub mod query;
pub mod update;
use crate::config::EnvironmentConfiguration;
pub use executable_query::ExecutableQuery;

static POOL: OnceLock<Pool<Postgres>> = OnceLock::new();
static REDIS: OnceLock<RedisClient> = OnceLock::new();

pub async fn initialize(config: &EnvironmentConfiguration) -> Result<()> {
  log::info!("Initializing database...");

  // Setup Postgres pool connection
  let pool = PgPoolOptions::new()
    .max_connections(5)
    .acquire_timeout(Duration::from_secs(30))
    .connect(&format!(
      "postgres://{}:{}@{}:{}/{}",
      config.postgres_user,
      config.postgres_password,
      config.postgres_host,
      config.postgres_port,
      config.postgres_database
    ))
    .await?;
  match POOL.set(pool) {
    Ok(_) => {}
    Err(_) => {
      log::warn!("Database pool already initialized");
    }
  }

  // Setup Redis connection
  let redis = {
    let host = std::env::var("VALKEY_HOST").unwrap_or("localhost".to_string());
    let port = std::env::var("VALKEY_PORT").unwrap_or("6379".to_string());
    let url = format!("redis://{}:{}", host, port);
    RedisClient::open(url).expect("Failed to create valkey client")
  };
  match REDIS.set(redis) {
    Ok(_) => {}
    Err(_) => {
      log::warn!("Valkey client already initialized");
    }
  }

  // Run migrations
  match run_migrations().await {
    Ok(_) => log::debug!("Successfully ran migrations"),
    Err(e) => log::error!("Failed to run migrations: {}", e),
  }

  log::info!("Database initialized");
  Ok(())
}

pub fn pool() -> &'static Pool<Postgres> {
  POOL.get().unwrap()
}

fn redis() -> &'static RedisClient {
  REDIS.get().unwrap()
}

pub fn redis_connection() -> RedisResult<redis::Connection> {
  let conn = redis().get_connection()?;
  Ok(conn)
}

pub async fn redis_async_connection() -> RedisResult<RedisConnection> {
  let conn = redis().get_multiplexed_async_connection().await?;
  Ok(conn)
}

async fn run_migrations() -> Result<()> {
  log::debug!("Running migrations");
  let pool = pool();
  sqlx::migrate!("../../migrations").run(pool).await?;
  Ok(())
}

#[derive(Debug, Clone)]
pub enum Value {
  Int(i32),
  OptionalInt(Option<i32>),
  BigInt(i64),
  OptionalBigInt(Option<i64>),
  Float(f32),
  OptionalFloat(Option<f32>),
  Double(f64),
  OptionalDouble(Option<f64>),
  Bool(bool),
  OptionalBool(Option<bool>),
  Text(String),
  OptionalText(Option<String>),
  DateTime(DateTime<Utc>),
  OptionalDateTime(Option<DateTime<Utc>>),
}

impl Display for Value {
  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    match self {
      Value::Int(n) => write!(f, "{}", n),
      Value::OptionalInt(Some(n)) => write!(f, "{}", n),
      Value::OptionalInt(None) => write!(f, "NULL"),
      Value::BigInt(n) => write!(f, "{}", n),
      Value::OptionalBigInt(Some(n)) => write!(f, "{}", n),
      Value::OptionalBigInt(None) => write!(f, "NULL"),
      Value::Float(n) => write!(f, "{}", n),
      Value::OptionalFloat(Some(n)) => write!(f, "{}", n),
      Value::OptionalFloat(None) => write!(f, "NULL"),
      Value::Double(n) => write!(f, "{}", n),
      Value::OptionalDouble(Some(n)) => write!(f, "{}", n),
      Value::OptionalDouble(None) => write!(f, "NULL"),
      Value::Bool(n) => write!(f, "{}", n),
      Value::OptionalBool(Some(n)) => write!(f, "{}", n),
      Value::OptionalBool(None) => write!(f, "NULL"),
      Value::Text(s) => write!(f, "'{}'", s.replace("'", "''")),
      Value::OptionalText(Some(s)) => write!(f, "'{}'", s.replace("'", "''")),
      Value::OptionalText(None) => write!(f, "NULL"),
      Value::DateTime(n) => write!(f, "{}", n),
      Value::OptionalDateTime(Some(n)) => write!(f, "{}", n),
      Value::OptionalDateTime(None) => write!(f, "NULL"),
    }
  }
}
