use std::path::Path;

use derive_more::{Debug, Deref};
use fjall::{
    CompressionType, Config, PartitionCreateOptions, PersistMode,
    compaction::{Leveled, Strategy},
};

use crate::utils::{DbInfo, Persist};

#[derive(Debug, Clone, Deref)]
pub struct LogDb {
    #[debug(skip)]
    #[deref]
    pub db: fjall::Keyspace,
    pub db_info: Persist<DbInfo>,
    pub path: String,
    #[debug(skip)]
    pub(crate) service_tree: fjall::PartitionHandle,
}

impl LogDb {
    #[tracing::instrument(name = "Database::new")]
    pub fn new<P>(path: P) -> Self
    where
        P: AsRef<Path> + std::fmt::Debug,
    {
        let db_path = path.as_ref().join("db");
        Self::with_config(path, Config::new(&db_path))
    }

    pub fn config<P: AsRef<Path>>(path: P) -> Config {
        Config::new(&path)
    }

    #[tracing::instrument(skip(config))]
    pub fn with_config<P>(path: P, config: Config) -> Self
    where
        P: AsRef<Path> + std::fmt::Debug,
    {
        let path = path.as_ref();
        let keyspace = config.open().expect("Failed to open keyspace");

        let db_info = Persist::<DbInfo>::open(path.join("db.info"));

        let create_options = PartitionCreateOptions::default()
            .compression(CompressionType::None)
            .compaction_strategy(Strategy::Leveled(Leveled::default()));

        let service_tree = keyspace
            .open_partition("__service_tree", create_options)
            .expect("Failed to open __service_tree partition");

        Self {
            db: keyspace,
            db_info,
            path: path.to_string_lossy().into_owned(),
            service_tree,
        }
    }

    pub fn open_tree(
        &self,
        name: &str,
        options: Option<PartitionCreateOptions>,
    ) -> fjall::PartitionHandle {
        let create_options = options.unwrap_or_else(|| {
            PartitionCreateOptions::default()
                .compression(CompressionType::None)
                .compaction_strategy(Strategy::Leveled(Leveled::default()))
        });

        self.db
            .open_partition(name, create_options)
            .expect("Failed to open partition")
    }

    #[tracing::instrument(skip(self), name = "Database::close", fields(path = self.path))]
    pub fn close(&self) {
        self.db_info.save();
        self.db
            .persist(PersistMode::SyncAll)
            .expect("Failed to persist");
    }
}
