use std::fmt::Debug;

use const_fnv1a_hash::fnv1a_hash_str_32;

use crate::{
    Cid, GetType, KeyScheme, Typ,
    types::{ArmourError, EntityAttribute},
};

pub enum MigrationRes<Item: Record> {
    /// value unchanged
    Unchanged(Item::SelfId, Item),
    /// value changed
    Changed {
        migration_type: MigrationType,
        new_key: Item::SelfId,
        new_value: Item,
    },
    /// value deleted
    Deleted,
}

/// migration fn (key, value)
pub type MigrationFn<Item, Val> = fn(&[u8], &Val) -> MigrationRes<Item>;

#[derive(Debug, Clone, Copy)]
pub enum MigrationType {
    /// only key changed
    Key,
    /// only value changed
    Value,
    /// both key and value changed
    Entry,
}

/// (version, migration fn)
/// - version, for which migration is applied
/// - migration_fn: fn(key, value) -> MigrationRes
pub type Migration<Item, Val> = (u16, MigrationFn<Item, Val>);

pub trait Record: Sized + GetType + 'static {
    type SelfId: Cid + Debug;
    type Value: Debug + Clone;
    const NAME: &'static str;
    const ID: u32 = fnv1a_hash_str_32(Self::NAME);
    /// actual version, increment version on schema change
    const VERSION: u16 = 0;
    /// migrations (version, migration_fn)
    /// - version saved in db, for which migration is applied (if equal, migration_fn called)
    /// - migration_fn: fn(key, value) -> MigrationRes
    const MIGRATIONS: &'static [Migration<Self, Self::Value>] = &[];
    const ATTRIBUTES: EntityAttribute = EntityAttribute::from_ty::<Self>();

    fn deser(bytes: &Self::Value) -> Self;

    fn deser_owned(bytes: Self::Value) -> Self {
        Self::deser(&bytes)
    }

    fn try_deser(bytes: &Self::Value) -> Result<Self, ArmourError> {
        Ok(Self::deser(bytes))
    }

    fn ser(&self) -> Self::Value;

    fn ser_owned(self) -> Self::Value {
        Self::ser(&self)
    }

    #[inline]
    fn deser_key(key: &[u8]) -> Self::SelfId {
        <Self as Record>::SelfId::from_bytes(key).expect("cannot deserialize key")
    }

    #[inline]
    fn try_deser_key(key: &[u8]) -> Result<Self::SelfId, ArmourError> {
        <Self as Record>::SelfId::from_bytes(key)
    }
}

pub const fn record_types_tuple<R>() -> (KeyScheme, Typ)
where
    R: Record + GetType,
{
    let key_type = R::SelfId::TY;
    let val_type = R::TYPE;
    (key_type, val_type)
}
