use std::{collections::BTreeMap, path::Path};

use derive_more::{Debug, Deref};
use fjall::{Database, DatabaseBuilder, Keyspace, KeyspaceCreateOptions, PersistMode};

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

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

impl Db {
    pub fn new<P>(path: P) -> Self
    where
        P: AsRef<Path>,
    {
        let builder = Self::builder(path.as_ref());
        Self::with_builder(path, builder)
    }

    pub fn builder<P: AsRef<Path>>(path: P) -> DatabaseBuilder<Database> {
        let db_path = path.as_ref().join("db");
        Database::builder(&db_path)
    }

    pub fn with_builder<P: AsRef<Path>>(path: P, config: DatabaseBuilder<Database>) -> Self {
        let db = config.open().expect("Failed to open keyspace");
        let meta_path = path.as_ref().join("db.info");
        let db_info = Persist::<DbInfo>::open(meta_path);

        let seq_tree = db
            .keyspace("__seq_tree", KeyspaceCreateOptions::default)
            .expect("Failed to open __seq_tree tree");

        Self {
            db,
            db_info,
            path: path.as_ref().to_string_lossy().into_owned(),
            seq_tree,
        }
    }

    pub fn open_tree(&self, name: &str, options: Option<KeyspaceCreateOptions>) -> Keyspace {
        self.db
            .keyspace(name, || options.unwrap_or_default())
            .expect("Failed to open tree")
    }

    pub fn get_keyspace(&self, name: &str) -> Option<Keyspace> {
        if !self.db.keyspace_exists(name) {
            return None;
        }
        self.db
            .keyspace(name, KeyspaceCreateOptions::default)
            .inspect_err(|err| {
                error!("{err}");
            })
            .ok()
    }

    pub fn get_all_keyspaces(&self) -> BTreeMap<String, Keyspace> {
        self.db_info
            .cloned()
            .collections
            .into_iter()
            .filter_map(|(name, _)| self.get_keyspace(&name).map(|ks| (name, ks)))
            .collect()
    }

    pub fn snapshot(&self) -> Snapshot {
        let inner = self.db.snapshot();
        Snapshot { inner }
    }

    #[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");
    }
}
