use core::borrow::Borrow;
use std::{
    fmt::Debug,
    ops::{Deref, RangeBounds},
};

use fjall::{PartitionCreateOptions, Slice};

use super::{ByteValue, KeyValue, builder::LogBuilder, db::LogDb, raw_tree::RawTree};
use crate::{Cid, DbError, DbResult, Entry, Record};

#[derive(Debug)]
pub struct LogTree<Item: Record> {
    pub(crate) raw: RawTree,
    pub(crate) phantom: std::marker::PhantomData<Item>,
}

impl<Item: Record> Clone for LogTree<Item> {
    fn clone(&self) -> Self {
        Self {
            raw: self.raw.clone(),
            phantom: std::marker::PhantomData,
        }
    }
}

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

    #[deprecated(note = "Use `open` instead")]
    pub fn open_tree(db: LogDb, options: Option<PartitionCreateOptions>) -> Self {
        LogBuilder::new::<Item>(db, options).build()
    }

    pub fn with_name(db: LogDb, name: &str, options: Option<PartitionCreateOptions>) -> Self {
        LogBuilder::with_name(db, name, options).build()
    }

    pub fn builder(db: LogDb, options: Option<PartitionCreateOptions>) -> LogBuilder {
        LogBuilder::new::<Item>(db, options)
    }

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

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

impl<Item: Record> Deref for LogTree<Item> {
    type Target = RawTree;

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

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

type ItemWithRaw<Item> = (ByteValue, ByteValue, Entry<Item>);

// update methods
impl<Item> LogTree<Item>
where
    Item: Record<Value = Slice> + Debug,
{
    /// Insert or update value by id.
    #[doc(alias = "insert")]
    #[doc(alias = "update")]
    #[tracing::instrument(level = "trace", skip(self), fields(name = Item::NAME), ret)]
    pub fn upsert(&self, id: impl Borrow<Item::SelfId> + Debug, item: &Item) -> DbResult<()> {
        let bytes = Item::ser(item);

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

        self.tree.insert(key_bytes.clone(), bytes)?;
        self.invalidate_hash(id.borrow());

        Ok(())
    }

    /// permanently remove item
    #[doc(alias = "delete")]
    #[tracing::instrument(level = "trace", skip(self), fields(name = Item::NAME), ret)]
    pub fn remove(&self, id: impl Borrow<Item::SelfId> + Debug) -> DbResult<()> {
        let key_bytes = Cid::encode(id.borrow());
        let key_bytes = ByteValue::from(key_bytes.as_ref());

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

    #[deprecated(note = "Use `remove` instead")]
    pub fn remove_item(&self, id: &Item::SelfId) -> DbResult<()>
    where
        Item::SelfId: AsRef<Item::SelfId> + Debug,
    {
        self.remove(id)
    }

    /// Remove item to other ('removed') tree
    #[doc(alias = "delete")]
    #[tracing::instrument(level = "trace", skip(self), fields(name = Item::NAME), ret)]
    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.removed.insert(key_bytes.clone(), val)?;
                self.tree.remove(key_bytes).map_err(DbError::from)?;
                self.invalidate_hash(id.borrow());
                Ok(Some(item))
            }
            None => Ok(None),
        }
    }

    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)
        }))
    }
}

// read methods
impl<Item> LogTree<Item>
where
    Item: Record<Value = Slice> + Debug,
{
    #[tracing::instrument(level = "trace", skip(self), fields(name = Item::NAME), ret)]
    fn get_by_slice(&self, key: &[u8]) -> DbResult<Option<Item>> {
        let val = self.tree.get(key)?;
        match val {
            Some(val) => Ok(Some(Item::deser_owned(val))),
            None => Ok(None),
        }
    }

    /// get value by id
    #[tracing::instrument(level = "trace", skip(self), fields(name = Item::NAME), ret)]
    pub fn get(&self, id: impl Borrow<Item::SelfId> + Debug) -> DbResult<Option<Item>> {
        let key = Cid::encode(id.borrow());
        self.get_by_slice(key.as_ref())
    }

    #[deprecated(note = "Use `get` instead")]
    pub fn get_item(&self, id: &Item::SelfId) -> DbResult<Option<Item>>
    where
        Item::SelfId: AsRef<Item::SelfId> + Debug,
    {
        self.get(id)
    }

    /// get value by id or return not found
    #[tracing::instrument(level = "trace", skip(self), fields(name = Item::NAME), ret)]
    pub fn get_or_err(&self, id: impl Borrow<Item::SelfId> + Debug) -> DbResult<Item> {
        let key = Cid::encode(id.borrow());
        self.get_by_slice(key.as_ref())
            .and_then(|item| item.ok_or(DbError::NotFound))
    }

    /// check exist by id
    #[doc(alias = "exists")]
    #[tracing::instrument(level = "trace", skip(self), fields(name = Item::NAME), ret)]
    pub fn contains(&self, id: impl Borrow<Item::SelfId> + Debug) -> DbResult<bool> {
        let key = Cid::encode(id.borrow());
        self.tree.contains_key(key.as_ref()).map_err(DbError::from)
    }

    /// get iterator for collection
    #[tracing::instrument(level = "trace", skip(self), fields(name = Item::NAME))]
    pub fn iter(&self) -> impl DoubleEndedIterator<Item = Entry<Item>> + use<Item> {
        self.tree.iter().filter_map(entry_filter_map)
    }

    /// get iterator by prefix for collection
    #[tracing::instrument(level = "trace", skip(self), fields(name = Item::NAME))]
    pub fn scan_prefix<P>(
        &self,
        prefix: P,
    ) -> impl DoubleEndedIterator<Item = Entry<Item>> + use<P, Item>
    where
        P: AsRef<[u8]> + Debug,
    {
        self.tree.prefix(prefix).filter_map(entry_filter_map)
    }

    /// get iterator by prefix for collection
    #[tracing::instrument(level = "trace", skip(self), fields(name = Item::NAME))]
    pub fn scan_prefix_key<P>(
        &self,
        prefix: P,
    ) -> impl DoubleEndedIterator<Item = Item::SelfId> + use<P, Item>
    where
        P: AsRef<[u8]> + Debug,
    {
        self.tree.prefix(prefix).filter_map(|entry| match entry {
            Ok((key, _)) => Some(Item::deser_key(&key)),
            Err(e) => {
                tracing::error!(%e);
                None
            }
        })
    }

    /// get iterator by range for collection
    #[tracing::instrument(level = "trace", skip(self), fields(name = Item::NAME))]
    pub fn range_iter<K, R>(
        &self,
        range: R,
    ) -> impl DoubleEndedIterator<Item = Entry<Item>> + use<K, R, Item>
    where
        K: AsRef<[u8]>,
        R: RangeBounds<K> + Debug,
    {
        self.tree.range(range).filter_map(entry_filter_map)
    }

    /// get iterator by prefix for collection
    #[tracing::instrument(level = "trace", skip(self), fields(name = Item::NAME))]
    pub fn try_scan_prefix<P>(
        &self,
        prefix: P,
    ) -> impl DoubleEndedIterator<Item = DbResult<Entry<Item>>> + use<P, Item>
    where
        P: AsRef<[u8]> + Debug,
    {
        self.tree.prefix(prefix).map(entry_map)
    }

    /// get iterator by prefix for collection
    #[tracing::instrument(level = "trace", skip(self), fields(name = Item::NAME))]
    pub fn try_scan_prefix_with_raw<P>(
        &self,
        prefix: P,
    ) -> impl DoubleEndedIterator<Item = DbResult<ItemWithRaw<Item>>> + use<P, Item>
    where
        P: AsRef<[u8]> + Debug,
    {
        self.tree.prefix(prefix).map(entry_map_with_raw)
    }

    /// get iterator by range for collection
    /// if item error, return Error
    #[tracing::instrument(level = "trace", skip(self), fields(name = Item::NAME))]
    pub fn try_range_iter<K, R>(
        &self,
        range: R,
    ) -> impl DoubleEndedIterator<Item = DbResult<ItemWithRaw<Item>>> + use<K, R, Item>
    where
        K: AsRef<[u8]>,
        R: RangeBounds<K> + Debug,
    {
        self.tree.range(range).map(|item| {
            item.map_err(DbError::from).map(|item| {
                let (key, value) = item;
                let id = Item::deser_key(&key);
                let val = Item::deser(&value);
                (key, value, Entry { key: id, val })
            })
        })
    }

    /// iter without deserialize value
    #[tracing::instrument(level = "trace", skip(self), fields(name = Item::NAME))]
    pub fn iter_keys(&self) -> impl DoubleEndedIterator<Item = Item::SelfId> + use<Item> {
        self.tree.keys().filter_map(|key| match key {
            Ok(key) => Some(Item::deser_key(&key)),
            Err(e) => {
                tracing::error!(%e);
                None
            }
        })
    }
}

fn entry_filter_map<Item: Record<Value = Slice>>(
    item: fjall::Result<KeyValue>,
) -> Option<Entry<Item>> {
    match item {
        Ok((key, value)) => {
            let id = Item::deser_key(&key);

            let value = Item::deser_owned(value);
            Some(Entry {
                key: id,
                val: value,
            })
        }
        Err(e) => {
            tracing::error!(%e);
            None
        }
    }
}

fn entry_map<Item: Record<Value = Slice>>(item: fjall::Result<KeyValue>) -> DbResult<Entry<Item>> {
    item.map_err(DbError::from).map(|item| {
        let (key, value) = item;
        let id = Item::deser_key(&key);
        let val = Item::deser_owned(value);
        Entry { key: id, val }
    })
}

fn entry_map_with_raw<Item: Record<Value = Slice>>(
    item: fjall::Result<KeyValue>,
) -> DbResult<ItemWithRaw<Item>> {
    item.map_err(DbError::from).map(|item| {
        let (key, value) = item;
        let id = Item::deser_key(&key);
        let val = Item::deser(&value);
        (key, value, Entry { key: id, val })
    })
}
