// file: kb_lib/src/db/connection.rs

//! Database connection facade.

/// Concrete database connection.
#[derive(Debug, Clone)]
pub enum DatabaseConnection {
    /// SQLite connection pool.
    Sqlite(sqlx::SqlitePool),
}

/// Database facade.
#[derive(Debug, Clone)]
pub struct Database {
    backend: crate::DatabaseBackend,
    database_url: std::string::String,
    connection: DatabaseConnection,
}

impl Database {
    /// Opens a database connection without initializing the schema.
    pub async fn connect(config: &crate::DatabaseConfig) -> Result<Self, crate::Error> {
        if !config.enabled {
            return Err(crate::Error::Config("database is disabled in configuration".to_string()));
        }
        match config.backend {
            crate::DatabaseBackend::Sqlite => {
                let database_url_result =
                    crate::db::sqlite::sqlite_database_url_from_config(config);
                let database_url = match database_url_result {
                    Ok(database_url) => database_url,
                    Err(error) => return Err(error),
                };
                let pool_result = crate::db::sqlite::connect_sqlite(config).await;
                let pool = match pool_result {
                    Ok(pool) => pool,
                    Err(error) => return Err(error),
                };
                return Ok(Self {
                    backend: crate::DatabaseBackend::Sqlite,
                    database_url,
                    connection: DatabaseConnection::Sqlite(pool),
                });
            },
        }
    }

    /// Opens a database connection and initializes the schema if configured.
    pub async fn connect_and_initialize(
        config: &crate::DatabaseConfig,
    ) -> Result<Self, crate::Error> {
        let connect_result = Self::connect(config).await;
        let database = match connect_result {
            Ok(database) => database,
            Err(error) => return Err(error),
        };
        if config.sqlite.auto_initialize_schema {
            let init_result = crate::db::schema::ensure_schema(&database).await;
            if let Err(error) = init_result {
                return Err(error);
            }
        }
        return Ok(database);
    }

    /// Returns the configured backend.
    pub fn backend(&self) -> crate::DatabaseBackend {
        return self.backend;
    }

    /// Returns a displayable database URL-like string.
    pub fn database_url(&self) -> &str {
        return &self.database_url;
    }

    /// Pings the database.
    pub async fn ping(&self) -> Result<(), crate::Error> {
        match &self.connection {
            DatabaseConnection::Sqlite(pool) => {
                let ping_result = sqlx::query("SELECT 1").execute(pool).await;
                match ping_result {
                    Ok(_) => return Ok(()),
                    Err(error) => {
                        return Err(crate::Error::Db(format!(
                            "cannot ping sqlite database '{}': {}",
                            self.database_url, error
                        )));
                    },
                }
            },
        }
    }

    /// Returns the underlying connection enum.
    pub(crate) fn connection(&self) -> &DatabaseConnection {
        return &self.connection;
    }
}

#[cfg(test)]
mod tests {
    #[tokio::test]
    async fn connect_and_ping_sqlite_database_works() {
        let tempdir = tempfile::tempdir().expect("tempdir must succeed");
        let database_path = tempdir.path().join("connection.sqlite3");
        let config = crate::DatabaseConfig {
            enabled: true,
            backend: crate::DatabaseBackend::Sqlite,
            sqlite: crate::SqliteDatabaseConfig {
                path: database_path.to_string_lossy().to_string(),
                create_if_missing: true,
                busy_timeout_ms: 5000,
                max_connections: 1,
                auto_initialize_schema: true,
                use_wal: true,
            },
        };
        let database = crate::Database::connect_and_initialize(&config)
            .await
            .expect("database init must succeed");
        assert_eq!(database.backend(), crate::DatabaseBackend::Sqlite);
        assert!(database.database_url().starts_with("sqlite://"));
        database.ping().await.expect("ping must succeed");
        let metadata = crate::query_db_metadatas_get(&database, "schema_version")
            .await
            .expect("metadata fetch must succeed");
        assert!(metadata.is_some());
    }
}
