use core::borrow::Borrow;
#[cfg(feature = "bytemuck")]
use core::marker::PhantomData;
use std::{
    fmt::Debug,
    io::Result as IoResult,
    iter::FilterMap,
    ops::{Bound, RangeBounds},
};

use sled::{CompareAndSwapError, CompareAndSwapSuccess, InlineArray};

use super::{ByteValue, Iter, KeyValue};
#[cfg(feature = "bytemuck")]
use crate::database::byte_as_slice::BytesAsValSlice;
use crate::{Cid, CollectionTree, Entry, Record, database::updater::Updater};
use crate::{
    error::{DbError, DbResult},
    replication::ChangeEvent,
};

pub type IterCollection<T> = FilterMap<Iter, fn(IoResult<KeyValue>) -> Option<T>>;
pub type ItemWithRaw<Item> = (ByteValue, ByteValue, Entry<Item>);
pub type ParIterBound = Bound<[u8; 4]>;
pub type ParIterBounds = (ParIterBound, ParIterBound);

// update methods
impl<Item> crate::CollectionTree<Item>
where
    Item: Record<Value = InlineArray> + Debug,
{
    /// Insert or update value by id.
    /// Return old value if exist
    #[doc(alias = "insert")]
    #[doc(alias = "update")]
    #[instrument(level = "trace", skip(self), fields(name = Item::NAME), ret)]
    pub fn upsert(
        &self,
        id: impl Borrow<Item::SelfId> + Debug,
        item: &Item,
    ) -> DbResult<Option<Item>> {
        let start = std::time::Instant::now();
        let bytes = Item::ser(item);
        let borrowed_id = id.borrow();

        let key_bytes = Cid::encode(borrowed_id);
        let key_bytes = InlineArray::from(AsRef::<[u8]>::as_ref(&key_bytes));

        let old = self
            .raw
            .tree
            .insert(&key_bytes, bytes.clone())
            .map(|i| i.map(|item| Item::deser(&item)))?;

        self.update_indexes(borrowed_id, old.as_ref(), Some(item));
        self.upsert_event(borrowed_id, old.as_ref(), item);
        self.invalidate_hash(borrowed_id);
        self.send_event(|| ChangeEvent::Upsert((key_bytes, bytes)));

        histogram!("armour_sled_upsert_duration", "name" => self.raw.name.clone())
            .record(start.elapsed().as_secs_f64());
        counter!("armour_sled_upsert_total", "name" => self.raw.name.clone()).increment(1);

        Ok(old)
    }

    /// Update, create or delete value by id with Fn. Can call closure multiple times.
    ///
    /// Return new value and closure result
    #[instrument(level = "trace", skip(self, f), fields(name = Item::NAME), ret)]
    pub fn upsert_fn<F, Ret>(
        &self,
        id: impl Borrow<Item::SelfId> + Debug,
        f: F,
    ) -> DbResult<(Option<Item>, Ret)>
    where
        F: Fn(Option<Item>) -> (Option<Item>, Ret),
        Item: Clone,
        Ret: Debug,
    {
        let start = std::time::Instant::now();
        let borrowed_id = id.borrow();
        let key = Cid::encode(borrowed_id);
        let mut old_bytes = self.raw.tree.get(&key)?;

        let res = (|| loop {
            let old_ref = old_bytes.as_ref();
            let old = old_ref.map(|old| Item::deser(old));
            let old_cloned = old.clone();

            let (new, ret) = f(old);
            let new_bytes = new.as_ref().map(|new| Item::ser(new));

            let res = self.raw.tree.compare_and_swap(&key, old_ref, new_bytes)?;

            match res {
                Ok(CompareAndSwapSuccess {
                    new_value,
                    previous_value,
                }) => {
                    if new_value == previous_value {
                        return Ok((new, ret));
                    }
                    self.update_indexes(borrowed_id, old_cloned.as_ref(), new.as_ref());
                    self.invalidate_hash(borrowed_id);

                    self.send_event(|| match new_value {
                        Some(bytes) => ChangeEvent::Upsert((ByteValue::from(key.as_ref()), bytes)),
                        None => ChangeEvent::Delete(ByteValue::from(key.as_ref())),
                    });

                    match &new {
                        Some(new) => {
                            self.upsert_event(borrowed_id, old_cloned.as_ref(), new);
                        }
                        None => {
                            if let Some(old) = old_cloned {
                                self.remove_event(borrowed_id, &old);
                            }
                        }
                    }
                    return Ok((new, ret));
                }
                Err(CompareAndSwapError {
                    current,
                    proposed: _,
                }) => {
                    old_bytes = current;
                }
            }
        })();

        histogram!("armour_sled_upsert_fn_duration", "name" => self.raw.name.clone())
            .record(start.elapsed().as_secs_f64());
        counter!("armour_sled_upsert_fn_total", "name" => self.raw.name.clone()).increment(1);

        res
    }

    /// Update item with closure if exist, return new value. Tried only once.
    #[instrument(level = "trace", skip(self, f), fields(name = Item::NAME), ret)]
    pub fn update_fn<F>(&self, id: impl Borrow<Item::SelfId> + Debug, f: F) -> DbResult<Item>
    where
        F: FnOnce(Item) -> DbResult<Item>,
        Item: Clone,
    {
        let start = std::time::Instant::now();
        let borrowed_id = id.borrow();
        let key = Cid::encode(borrowed_id);
        let old_bytes = self.raw.tree.get(&key)?;
        let old = old_bytes.as_ref().map(|old| Item::deser(old));

        let res = match (old, old_bytes) {
            (Some(old), Some(old_bytes)) => {
                let old_cloned = old.clone();

                let new = f(old)?;

                let new_bytes = Item::ser(&new);

                let res = self.raw.tree.compare_and_swap(
                    &key,
                    Some(old_bytes.as_ref()),
                    Some(new_bytes),
                )?;

                match res {
                    Ok(CompareAndSwapSuccess {
                        new_value,
                        previous_value: _,
                    }) => {
                        let new_bytes = new_value.expect("new value should be present");
                        self.invalidate_hash(borrowed_id);
                        self.update_indexes(borrowed_id, Some(&old_cloned), Some(&new));
                        self.upsert_event(borrowed_id, Some(&old_cloned), &new);
                        self.send_event(|| {
                            let key_bytes = ByteValue::from(key.as_ref());
                            ChangeEvent::Upsert((key_bytes, new_bytes))
                        });

                        Ok(new)
                    }
                    Err(_) => Err(DbError::Transaction),
                }
            }
            _ => Err(DbError::NotFound),
        };

        histogram!("armour_sled_update_fn_duration", "name" => self.raw.name.clone())
            .record(start.elapsed().as_secs_f64());
        counter!("armour_sled_update_fn_total", "name" => self.raw.name.clone()).increment(1);

        res
    }

    pub fn updater(&self, id: Item::SelfId) -> DbResult<Updater<Item>>
    where
        Item: Clone,
    {
        let start = std::time::Instant::now();
        let key = Cid::encode(&id);
        let val = self.tree.get(key)?.ok_or(DbError::NotFound)?;
        let item = Item::deser(&val);
        let updater = Updater {
            id,
            val: item.clone(),
            bytes: val,
            old: item,
        };

        histogram!("armour_sled_updater_duration", "name" => self.raw.name.clone())
            .record(start.elapsed().as_secs_f64());
        counter!("armour_sled_updater_total", "name" => self.raw.name.clone()).increment(1);

        Ok(updater)
    }

    /// Tried only once.
    pub fn apply_updater(&self, updater: Updater<Item>) -> DbResult<Entry<Item>> {
        let start = std::time::Instant::now();
        let Updater {
            id,
            val,
            bytes,
            old,
        } = updater;
        let borrowed_id = id.borrow();
        let key = Cid::encode(borrowed_id);
        let new = val.ser();
        let CompareAndSwapSuccess {
            new_value: _,
            previous_value: _,
        } = self
            .tree
            .compare_and_swap(&key, Some(bytes), Some(new.clone()))?
            .map_err(|_| DbError::Transaction)?;

        self.invalidate_hash(borrowed_id);
        self.update_indexes(borrowed_id, Some(&old), Some(&val));
        self.upsert_event(borrowed_id, Some(&old), &val);
        let key_bytes = ByteValue::from(key.as_ref());
        let event = ChangeEvent::Upsert((key_bytes, new));
        self.send_event(|| event);

        let res = Ok(Entry::new(id, val));

        histogram!("armour_sled_apply_updater_duration", "name" => self.raw.name.clone())
            .record(start.elapsed().as_secs_f64());
        counter!("armour_sled_apply_updater_total", "name" => self.raw.name.clone()).increment(1);

        res
    }

    /// change status in item key with closure,
    /// if key not exist, does nothing.
    /// not use for change id!
    #[instrument(level = "trace", skip(self), fields(name = Item::NAME), err)]
    #[deprecated]
    pub fn change_status(&self, old_id: &Item::SelfId, new_id: &Item::SelfId) -> DbResult<()> {
        let start = std::time::Instant::now();
        let old_key_bytes = Cid::encode(old_id);
        let new_key_bytes = Cid::encode(new_id);
        // TODO: run in transaction
        let value = self
            .raw
            .tree
            .remove(&old_key_bytes)?
            .ok_or(DbError::NotFound)?;
        let item = Item::deser(&value);
        if let Some(val) = self.raw.tree.insert(&new_key_bytes, value)? {
            let item = Item::deser(&val);
            error!(?item, "unexpected value");
        };
        self.update_indexes(old_id, Some(&item), None);
        self.update_indexes(new_id, None, Some(&item));
        self.id_change_event(&item, old_id, new_id);
        self.invalidate_hash(old_id);
        self.invalidate_hash(new_id);
        self.send_event(|| {
            let old_key_bytes = ByteValue::from(old_key_bytes.as_ref());
            let new_key_bytes = ByteValue::from(new_key_bytes.as_ref());
            ChangeEvent::ChangeId(old_key_bytes, new_key_bytes)
        });

        histogram!("armour_sled_change_status_duration", "name" => self.raw.name.clone())
            .record(start.elapsed().as_secs_f64());
        counter!("armour_sled_change_status_total", "name" => self.raw.name.clone()).increment(1);

        Ok(())
    }

    /// permanently remove item. Return old value or None if not exist
    #[doc(alias = "delete")]
    #[instrument(level = "trace", skip(self), fields(name = Item::NAME), ret)]
    pub fn remove(&self, id: impl Borrow<Item::SelfId> + Debug) -> DbResult<Option<Item>> {
        let start = std::time::Instant::now();
        let borrowed_id = id.borrow();
        let key_bytes = Cid::encode(borrowed_id);

        let res = self
            .raw
            .tree
            .remove(&key_bytes)
            .map(|res| res.map(|val| Item::deser_owned(val)))
            .map_err(DbError::from);

        if let Ok(Some(item)) = &res {
            self.update_indexes(borrowed_id, Some(item), None);
            self.remove_event(borrowed_id, item);
            self.invalidate_hash(borrowed_id);
            self.send_event(|| {
                let key_bytes = ByteValue::from(key_bytes.as_ref());
                ChangeEvent::Delete(key_bytes)
            });
        }

        histogram!("armour_sled_remove_duration", "name" => self.raw.name.clone())
            .record(start.elapsed().as_secs_f64());
        counter!("armour_sled_remove_total", "name" => self.raw.name.clone()).increment(1);

        res
    }

    /// Remove item to other ('removed') tree. Return old value or None if not exist
    #[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 start = std::time::Instant::now();
        let borrowed_id = id.borrow();
        let key_bytes = Cid::encode(borrowed_id);

        let res = self.raw.tree.remove(&key_bytes)?;

        let res = match res {
            Some(res) => {
                let item = Item::deser(&res);
                self.update_indexes(borrowed_id, Some(&item), None);
                self.remove_event(borrowed_id, &item);
                self.invalidate_hash(borrowed_id);
                self.send_event(|| {
                    let key_bytes = ByteValue::from(key_bytes.as_ref());
                    ChangeEvent::Delete(key_bytes)
                });
                self.removed.insert(key_bytes, res)?;
                Ok(Some(item))
            }
            None => Ok(None),
        };

        histogram!("armour_sled_soft_remove_duration", "name" => self.raw.name.clone())
            .record(start.elapsed().as_secs_f64());
        counter!("armour_sled_soft_remove_total", "name" => self.raw.name.clone()).increment(1);

        res
    }
}

// read methods
impl<Item> CollectionTree<Item>
where
    Item: Record<Value = InlineArray> + Debug,
{
    #[instrument(level = "trace", skip(self), fields(name = Item::NAME), ret)]
    fn get_by_slice(&self, key: &[u8]) -> DbResult<Option<Item>> {
        // self.raw.tree.get_zero_copy(key, |bytes| bytes.map(Self::deser)).map_err(DbError::from)
        let val = self.raw.tree.get(key)?;
        match val {
            Some(val) => Ok(Some(Item::deser_owned(val))),
            None => Ok(None),
        }
    }

    /// get value by id
    #[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())
    }

    /// get value by id or return not found
    #[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 = "exist")]
    #[instrument(level = "trace", skip(self), fields(name = Item::NAME), ret)]
    pub fn contains(&self, id: impl Borrow<Item::SelfId> + Debug) -> DbResult<bool> {
        let start = std::time::Instant::now();
        let key = Cid::encode(id.borrow());
        let res = self
            .raw
            .tree
            .contains_key(key.as_ref())
            .map_err(DbError::from);

        histogram!("armour_sled_contains_duration", "name" => self.raw.name.clone())
            .record(start.elapsed().as_secs_f64());
        counter!("armour_sled_contains_total", "name" => self.raw.name.clone()).increment(1);

        res
    }

    /// get iterator for collection
    #[instrument(level = "trace", skip(self), fields(name = Item::NAME))]
    pub fn iter(&self) -> IterCollection<Entry<Item>> {
        self.range_iter::<&[u8], _>(..)
    }

    /// get iterator by prefix for collection
    #[instrument(level = "trace", skip(self), fields(name = Item::NAME))]
    pub fn scan_prefix<P>(&self, prefix: P) -> IterCollection<Entry<Item>>
    where
        P: AsRef<[u8]> + Debug,
    {
        let from = prefix.as_ref();
        let mut upper = from.to_vec();

        // [0,0,100,100] -> [0,0,100,100]..[0,0,100,101]
        // [0,0,100,255] -> [0,0,100,255]..[0,0,101]
        // [255,255] -> [255,255]..
        while let Some(last) = upper.pop() {
            if last < u8::MAX {
                upper.push(last + 1);
                return self.range_iter(from..upper.as_ref());
            }
        }

        self.range_iter(from..)
    }

    /// get iterator by range for collection
    #[instrument(level = "trace", skip(self), fields(name = Item::NAME))]
    pub fn range_iter<K, R>(&self, range: R) -> IterCollection<Entry<Item>>
    where
        K: AsRef<[u8]>,
        R: RangeBounds<K> + Debug,
    {
        counter!("armour_sled_range_iter_total", "name" => self.raw.name.clone()).increment(1);
        self.raw.tree.range(range).filter_map(|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) => {
                error!(%e);
                None
            }
        })
    }

    /// get iterator by range for collection
    /// if item error, return Error
    #[instrument(level = "trace", skip(self), fields(name = Item::NAME))]
    pub fn try_range_iter<K, R>(
        &self,
        range: R,
    ) -> impl DoubleEndedIterator<Item = DbResult<Entry<Item>>> + use<K, R, Item>
    where
        K: AsRef<[u8]>,
        R: RangeBounds<K> + Debug,
    {
        counter!("armour_sled_try_range_iter_total", "name" => self.raw.name.clone()).increment(1);
        self.raw.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_owned(value);
                Entry { key: id, val }
            })
        })
    }

    /// get iterator by range for collection
    /// if item error, return Error
    #[instrument(level = "trace", skip(self), fields(name = Item::NAME))]
    pub fn try_range_iter_with_raw<K, R>(
        &self,
        range: R,
    ) -> impl DoubleEndedIterator<Item = DbResult<ItemWithRaw<Item>>> + use<K, R, Item>
    where
        K: AsRef<[u8]>,
        R: RangeBounds<K> + Debug,
    {
        counter!("armour_sled_try_range_iter_with_raw_total", "name" => self.raw.name.clone())
            .increment(1);
        self.raw.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
    #[instrument(level = "trace", skip(self), fields(name = Item::NAME))]
    pub fn iter_keys<P>(
        &self,
        prefix: P,
    ) -> impl DoubleEndedIterator<Item = Item::SelfId> + use<P, Item>
    where
        P: AsRef<[u8]> + Debug,
    {
        counter!("armour_sled_iter_keys_total", "name" => self.raw.name.clone()).increment(1);
        self.raw
            .tree
            .scan_prefix(prefix)
            .filter_map(|res| match res {
                Ok((key, _)) => Some(Item::deser_key(&key)),
                Err(e) => {
                    error!(%e);
                    None
                }
            })
    }

    #[cfg(feature = "bytemuck")]
    pub fn cast_slice<K: AsRef<[u8]>>(
        &self,
        id: K,
    ) -> DbResult<Option<BytesAsValSlice<InlineArray, Item>>> {
        let val = self.raw.tree.get(id)?;
        match val {
            Some(val) => Ok(Some(BytesAsValSlice(val, PhantomData))),
            None => Ok(None),
        }
    }
}
