Skip to content
use std::path::Path;
use std::sync::Arc;
use std::time::Duration;

use tokio::sync::OnceCell;
use turso::Builder;

pub mod invites;
pub mod migration;
pub mod session_store;
pub mod tickets;
pub mod users;

#[derive(Clone)]
pub struct Database {
    db: Arc<OnceCell<turso::Database>>,
    db_path: String,
}

impl Database {
    pub fn new(db_path: &str) -> Self {
        Self {
            db: Arc::new(OnceCell::new()),
            db_path: db_path.to_owned(),
        }
    }

    #[cfg(test)]
    pub fn db_path(&self) -> &str {
        &self.db_path
    }

    pub async fn conn(&self) -> Result<turso::Connection, String> {
        let db = self
            .db
            .get_or_try_init(|| async {
                let db_path = Path::new(&self.db_path);
                let parent = db_path.parent().unwrap_or(Path::new("."));
                if !parent.exists() {
                    log::info!("Creating parent directory: {}", parent.display());
                    std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
                }

                let db_path_str = db_path.to_str().ok_or_else(|| {
                    "Invalid database path: contains non-UTF8 characters".to_string()
                })?;

                let db = Builder::new_local(db_path_str).build().await.map_err(|e| {
                    log::error!("Failed to create database: {e}");
                    e.to_string()
                })?;

                log::info!("Created db");
                Ok::<turso::Database, String>(db)
            })
            .await?;

        let conn = db
            .connect()
            .map_err(|e| format!("Failed to connect to database: {e}"))?;
        conn.busy_timeout(Duration::from_secs(30))
            .map_err(|e| format!("Failed to set busy timeout: {e}"))?;
        Ok(conn)
    }

    pub async fn init(&self) -> Result<(), String> {
        self.create_tables().await?;

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

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

    #[test]
    fn test_database_new_stores_path() {
        let db = Database::new("/tmp/test_pear_new.db");
        assert_eq!(db.db_path(), "/tmp/test_pear_new.db");
    }

    #[tokio::test]
    async fn test_database_init_creates_file() {
        let db_path = format!("/tmp/test_pear_init_{}.db", uuid::Uuid::new_v4());
        let db = Database::new(&db_path);

        assert!(db.init().await.is_ok());
        assert!(std::path::Path::new(&db_path).exists());

        let _ = std::fs::remove_file(&db_path);
    }
}