use std::{borrow::Borrow, fmt::Debug, ops::Deref, sync::Arc};

use armour_derive::armour_metrics;
use fjall::{Keyspace, KeyspaceCreateOptions, Slice};

use super::{ByteValue, RawTree, builder::TreeBuilder, db::Db, events::change::*, read::ReadTree};
use crate::{Cid, DbError, DbResult, Record, indexes::IndexUpdateCollection};
use arrayvec::ArrayVec;

pub type IndexesMap<Item> = ArrayVec<Box<dyn IndexUpdateCollection<Item>>, 8>;

#[derive(derive_more::Debug)]
pub struct TypedTree<Item: Record> {
    pub inner: ReadTree<Item>,
    pub(crate) handlers: Arc<LogDbEventHandlers<Item>>,
}

impl<Item: Record> Clone for TypedTree<Item> {
    fn clone(&self) -> Self {
        Self {
            inner: self.inner.clone(),
            handlers: self.handlers.clone(),
        }
    }
}

impl<Item: Record> Deref for TypedTree<Item> {
    type Target = ReadTree<Item>;

    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}

impl<Item> TypedTree<Item>
where
    Item: Record<Value = Slice> + std::fmt::Debug,
{
    #[doc(alias = "new")]
    #[doc(alias = "create")]
    pub fn open(db: &Db, options: Option<KeyspaceCreateOptions>) -> Self {
        TreeBuilder::<Item>::new(db, options).build()
    }

    pub fn with_name(db: &Db, name: &str, options: Option<KeyspaceCreateOptions>) -> Self {
        TreeBuilder::<Item>::with_name(db, name, options).build()
    }

    pub fn builder(db: &Db, options: Option<KeyspaceCreateOptions>) -> TreeBuilder<Item> {
        TreeBuilder::<Item>::new(db, options)
    }

    #[doc(alias = "inner")]
    pub fn raw(&self) -> &RawTree {
        &self.inner.raw
    }

    pub(crate) fn tree(&self) -> &Keyspace {
        &self.inner.raw.tree
    }

    #[inline]
    pub(super) fn invalidate_hash(&self, id: &Item::SelfId) {
        let group = id.group_id();
        self.inner.raw.inner.invalidate_hash(group);
    }

    /// update fn when update item body or create item
    #[inline]
    pub(super) fn upsert_event(&self, id: &Item::SelfId, new: &Item) {
        if let Some(f) = self.handlers.on_upsert.as_ref() {
            f(id, new);
        }
    }

    /// call when item removed (soft or hard)
    #[inline]
    pub(super) fn remove_event(&self, id: &Item::SelfId) {
        if let Some(f) = self.handlers.on_remove.as_ref() {
            f(id);
        }
    }
}

impl<Item: Record> From<TypedTree<Item>> for RawTree {
    fn from(value: TypedTree<Item>) -> Self {
        value.inner.raw
    }
}

// update methods
impl<Item> TypedTree<Item>
where
    Item: Record<Value = Slice> + Debug,
{
    /// Insert or update value by id.
    #[instrument(level = "debug", skip_all, fields(name = Item::NAME))]
    #[armour_metrics(prefix = "armour_logdb_tree", name = self.raw().static_name())]
    pub fn upsert(&self, id: impl Borrow<Item::SelfId> + Debug, item: &Item) -> DbResult<()> {
        let bytes = Item::ser(item);

        let key = Cid::encode(id.borrow());
        let key_bytes = ByteValue::from(key.as_ref());

        self.tree().insert(key_bytes, bytes)?;

        self.upsert_event(id.borrow(), item);
        self.invalidate_hash(id.borrow());

        Ok(())
    }

    /// Insert value only if it does not exist
    #[doc(alias = "create")]
    #[instrument(level = "debug", skip_all, fields(name = Item::NAME))]
    #[armour_metrics(prefix = "armour_logdb_tree", name = self.raw().static_name())]
    pub fn insert(&self, id: impl Borrow<Item::SelfId> + Debug, item: &Item) -> DbResult<()> {
        let bytes = Item::ser(item);

        let key = Cid::encode(id.borrow());
        let key_bytes = ByteValue::from(key.as_ref());

        if self.tree().get(key.as_ref())?.is_some() {
            return Err(DbError::Client("already exists"));
        }

        self.tree().insert(key_bytes, bytes)?;

        self.upsert_event(id.borrow(), item);
        self.invalidate_hash(id.borrow());

        Ok(())
    }

    /// Update value and return old one
    #[doc(alias = "replace")]
    #[instrument(level = "debug", skip_all, fields(name = Item::NAME))]
    #[armour_metrics(prefix = "armour_logdb_tree", name = self.raw().static_name())]
    pub fn update(
        &self,
        id: impl Borrow<Item::SelfId> + Debug,
        item: &Item,
    ) -> DbResult<Option<Item>> {
        let bytes = Item::ser(item);

        let key = Cid::encode(id.borrow());
        let key_bytes = ByteValue::from(key.as_ref());

        match self.tree().get(key.as_ref())? {
            Some(val) => {
                let old_item = Item::deser(&val);

                self.tree().insert(key_bytes, bytes)?;
                self.upsert_event(id.borrow(), item);
                self.invalidate_hash(id.borrow());
                Ok(Some(old_item))
            }
            None => Ok(None),
        }
    }

    /// permanently remove item
    #[doc(alias = "delete")]
    #[instrument(level = "debug", skip_all, fields(name = Item::NAME))]
    #[armour_metrics(prefix = "armour_logdb_tree", name = self.raw().static_name())]
    pub fn remove(&self, id: impl Borrow<Item::SelfId> + Debug) -> DbResult<()> {
        let key = Cid::encode(id.borrow());
        let key_bytes = ByteValue::from(key.as_ref());

        self.tree().remove(key_bytes).map_err(DbError::from)?;
        self.remove_event(id.borrow());
        self.invalidate_hash(id.borrow());

        Ok(())
    }

    /// Remove item and return old value
    #[instrument(level = "debug", skip_all, fields(name = Item::NAME))]
    #[armour_metrics(prefix = "armour_logdb_tree", name = self.raw().static_name())]
    pub fn take(&self, id: impl Borrow<Item::SelfId> + Debug) -> DbResult<Option<Item>> {
        let key = Cid::encode(id.borrow());
        let key_bytes = ByteValue::from(key.as_ref());

        match self.tree().get(key.as_ref())? {
            Some(val) => {
                let item = Item::deser(&val);

                self.tree().remove(key_bytes).map_err(DbError::from)?;
                self.remove_event(id.borrow());
                self.invalidate_hash(id.borrow());
                Ok(Some(item))
            }
            None => Ok(None),
        }
    }

    /// Remove item to other ('removed') tree
    #[doc(alias = "delete")]
    #[instrument(level = "debug", skip_all, fields(name = Item::NAME))]
    #[armour_metrics(prefix = "armour_logdb_tree", name = self.raw().static_name())]
    pub fn soft_remove(&self, id: impl Borrow<Item::SelfId> + Debug) -> DbResult<Option<Item>> {
        let key = Cid::encode(id.borrow());
        let key_bytes = ByteValue::from(key.as_ref());
        match self.tree().get(key.as_ref())? {
            Some(val) => {
                let item = Item::deser(&val);

                self.raw().removed.insert(key_bytes.clone(), val)?;
                self.remove_event(id.borrow());
                self.tree().remove(key_bytes).map_err(DbError::from)?;
                self.invalidate_hash(id.borrow());
                Ok(Some(item))
            }
            None => Ok(None),
        }
    }

    /// Remove item to other ('removed') tree and return old value
    #[instrument(level = "debug", skip_all, fields(name = Item::NAME))]
    #[armour_metrics(prefix = "armour_logdb_tree", name = self.raw().static_name())]
    pub fn soft_take(&self, id: impl Borrow<Item::SelfId> + Debug) -> DbResult<Option<Item>> {
        let key = Cid::encode(id.borrow());
        let key_bytes = ByteValue::from(key.as_ref());
        match self.tree().get(key.as_ref())? {
            Some(val) => {
                let item = Item::deser(&val);

                self.raw().removed.insert(key_bytes.clone(), val)?;
                self.remove_event(id.borrow());
                self.tree().remove(key_bytes).map_err(DbError::from)?;
                self.invalidate_hash(id.borrow());
                Ok(Some(item))
            }
            None => Ok(None),
        }
    }

    /// doesn't change indexes
    #[armour_metrics(prefix = "armour_logdb_tree", name = self.raw().static_name())]
    pub fn apply_batch<K, V>(&self, iter: impl Iterator<Item = (K, Option<V>)>) -> DbResult<()>
    where
        K: AsRef<Item::SelfId>,
        V: AsRef<Item>,
    {
        self.raw().apply_batch(iter.map(|(id, item)| {
            let key_bytes = Cid::encode(id.as_ref());
            let key_bytes = ByteValue::from(key_bytes.as_ref());
            let val_bytes = item.map(|item| Item::ser(item.as_ref()));
            (key_bytes, val_bytes)
        }))
    }

    pub fn next_id(&self) -> DbResult<u64> {
        self.raw().next_id()
    }
}
