use std::sync::Arc;
use std::sync::{
    LazyLock,
    Mutex,
};

use sqlx::SqlitePool;

use crate::db::{
    create_db_table,
    get_db_pool,
};

#[derive(Debug, Clone, PartialEq, Default, Copy)]
pub enum DatabaseMode {
    #[default]
    File,
    Memory,
}

pub struct DatabaseContext {
    pub pool: Arc<SqlitePool>,
    pub mode: DatabaseMode,
}

pub struct DatabaseManager;

static MEMORY_DB_POOL: LazyLock<Mutex<Option<Arc<SqlitePool>>>> =
    LazyLock::new(|| Mutex::new(None));

impl DatabaseManager {
    pub async fn get_context(mode: DatabaseMode) -> Result<DatabaseContext, String> {
        match mode {
            DatabaseMode::File => {
                let pool = get_db_pool().await.map_err(|e| e.to_string())?;
                Ok(DatabaseContext { pool, mode })
            }
            DatabaseMode::Memory => {
                {
                    let pool_guard = MEMORY_DB_POOL.lock().unwrap();
                    if let Some(pool) = pool_guard.as_ref() {
                        return Ok(DatabaseContext {
                            pool: pool.clone(),
                            mode,
                        });
                    }
                }

                let connection_string = "sqlite::memory:";

                let pool = Arc::new(
                    SqlitePool::connect(connection_string)
                        .await
                        .map_err(|e| e.to_string())?,
                );
                create_db_table(&pool).await.map_err(|e| e.to_string())?;
                crate::utils::migration::migrate_configs(Some(&pool))
                    .await
                    .map_err(|e| e.to_string())?;

                {
                    let mut pool_guard = MEMORY_DB_POOL.lock().unwrap();
                    *pool_guard = Some(pool.clone());
                }

                Ok(DatabaseContext { pool, mode })
            }
        }
    }

    pub fn cleanup_memory_pools() {
        let mut pool_guard = MEMORY_DB_POOL.lock().unwrap();
        *pool_guard = None;
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_database_mode_default() {
        let mode = DatabaseMode::default();
        assert_eq!(mode, DatabaseMode::File);
    }

    #[tokio::test]
    async fn test_database_context_memory() {
        let context = DatabaseManager::get_context(DatabaseMode::Memory)
            .await
            .unwrap();
        assert_eq!(context.mode, DatabaseMode::Memory);
        assert!(!context.pool.is_closed());
    }

    #[tokio::test]
    async fn test_database_context_file() {
        let context = DatabaseManager::get_context(DatabaseMode::File).await;
        if let Ok(ctx) = context {
            assert_eq!(ctx.mode, DatabaseMode::File);
        }
    }
}
