use super::error::RedbResult;
use crate::codec::CodecError;
use redb::{ReadTransaction, TableDefinition, WriteTransaction};
use serde::Serialize;
use serde::de::DeserializeOwned;

pub(super) const TABLE_DATA: TableDefinition<&str, &[u8]> = TableDefinition::new("data");
pub(super) const TABLE_LOG: TableDefinition<u64, &str> = TableDefinition::new("inbox_log");
pub(super) const TABLE_META: TableDefinition<&str, &[u8]> = TableDefinition::new("metadata");
pub(super) const TABLE_DIFF_LOG: TableDefinition<&str, &[u8]> = TableDefinition::new("diff_log");
pub(super) const TABLE_MIGRATION_LOG: TableDefinition<&str, &[u8]> =
    TableDefinition::new("migration_log");
pub(super) const TABLE_SCHEMA_SNAPSHOT: TableDefinition<&str, &[u8]> =
    TableDefinition::new("schema_snapshot");

pub(super) trait TableReader {
    fn load_typed<T: DeserializeOwned>(
        &self,
        table_def: TableDefinition<&str, &[u8]>,
        key: &str,
    ) -> RedbResult<Option<T>>;
}

pub(super) trait TableWriter {
    fn save_typed<T: Serialize>(
        &self,
        table_def: TableDefinition<&str, &[u8]>,
        key: &str,
        val: &T,
    ) -> RedbResult<()>;
}

fn deserialize_from_table<T: DeserializeOwned>(
    table: impl redb::ReadableTable<&'static str, &'static [u8]>,
    key: &str,
) -> RedbResult<Option<T>> {
    table
        .get(key)?
        .map(|v| rmp_serde::from_slice(v.value()).map_err(CodecError::from))
        .transpose()
        .map_err(Into::into)
}

impl TableReader for ReadTransaction {
    fn load_typed<T: DeserializeOwned>(
        &self,
        table_def: TableDefinition<&str, &[u8]>,
        key: &str,
    ) -> RedbResult<Option<T>> {
        let table = self.open_table(table_def)?;
        deserialize_from_table(table, key)
    }
}

impl TableReader for WriteTransaction {
    fn load_typed<T: DeserializeOwned>(
        &self,
        table_def: TableDefinition<&str, &[u8]>,
        key: &str,
    ) -> RedbResult<Option<T>> {
        let table = self.open_table(table_def)?;
        deserialize_from_table(table, key)
    }
}

impl TableWriter for WriteTransaction {
    fn save_typed<T: Serialize>(
        &self,
        table_def: TableDefinition<&str, &[u8]>,
        key: &str,
        val: &T,
    ) -> RedbResult<()> {
        let mut table = self.open_table(table_def)?;
        let bytes = rmp_serde::to_vec(val).map_err(CodecError::from)?;
        table.insert(key, bytes.as_slice())?;
        Ok(())
    }
}
