use crate::commons::utils::CONFIG as config;
use crate::db::mysql::MysqlDb;
use crate::db::pg::PgDb;
use async_trait::async_trait;
use serde_json::Value;
use tokio::sync::OnceCell;

pub mod models;
pub mod mysql;
pub mod pg;

#[async_trait]
trait DbDriver: Send + Sync {
    async fn query(&self, sql: &str, params: Vec<Value>) -> Result<Vec<Value>, String>;
    async fn transaction(&self, queries: Vec<Value>) -> Result<Vec<Value>, String>;
}

static DB_CONN: OnceCell<Box<dyn DbDriver>> = OnceCell::const_new();

pub async fn init_db() -> Result<(), String> {
    if let Some(db) = &config.db {
        let conn: Box<dyn DbDriver> = match db.kind.as_str() {
            "postgresql" => Box::new(PgDb::new().await?),
            "mysql" => Box::new(MysqlDb::new().await?),
            other => {
                return Err(format!("unsupported db kind: {other}"));
            }
        };

        DB_CONN
            .set(conn)
            .map_err(|_| "db already initialized".to_string())
    } else {
        Ok(())
    }
}

pub async fn db_query(sql: &str, params: Vec<Value>) -> Result<Vec<Value>, String> {
    let conn = DB_CONN
        .get()
        .ok_or_else(|| "db not initialized".to_string())?;
    conn.query(sql, params).await
}

pub async fn db_transaction(queries: Vec<Value>) -> Result<Vec<Value>, String> {
    let conn = DB_CONN
        .get()
        .ok_or_else(|| "db not initialized".to_string())?;
    conn.transaction(queries).await
}
