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

use scc::TreeIndex;

use super::IndexUpdateCollection;
use crate::Record;

pub type BTreeIndexExtractor<Item, Id, TreeKey> = fn(&Item, &Id) -> Option<TreeKey>;

/// CompositionIndex is a unique hash index for computed value with getter function
///
/// Stored in memory, not persisted and builded on database open
#[derive(Clone, Deref)]
pub struct BTreeIndex<Key, Item: Record> {
    #[deref]
    pub map: Arc<TreeIndex<Key, Item::SelfId>>,
    getter: BTreeIndexExtractor<Item, Item::SelfId, Key>,
}

impl<Key, Item> BTreeIndex<Key, Item>
where
    Item: Record,
{
    pub fn new(getter: BTreeIndexExtractor<Item, Item::SelfId, Key>) -> Self {
        let map = TreeIndex::new();
        let map = Arc::new(map);
        Self { map, getter }
    }
}

impl<Key, Item> BTreeIndex<Key, Item>
where
    Item: Record,
    Item::SelfId: Copy,
    Key: Ord + Eq + Debug + Clone + 'static,
{
    pub fn get<Q>(&self, field: &Q) -> Option<Item::SelfId>
    where
        Key: Borrow<Q>,
        Q: Ord + Eq + ?Sized,
    {
        self.map.peek_with(field, |_, v| *v)
    }

    pub fn exist<Q>(&self, field: &Q) -> bool
    where
        Key: Borrow<Q>,
        Q: Ord + Eq + ?Sized,
    {
        self.map.contains(field)
    }

    fn remove(&self, field: Key) {
        if !self.map.remove_sync(&field) {
            error!(?field, "index not exists");
        }
    }
}

impl<Key, Item> IndexUpdateCollection<Item> for BTreeIndex<Key, Item>
where
    Key: Sync + Send + Eq + Ord + Debug + Clone + 'static,
    Item: Record,
    Item::SelfId: Sync + Send + Copy,
{
    fn update(&self, id: &Item::SelfId, old: Option<&Item>, new: Option<&Item>) {
        let getter = self.getter;
        let old_key = old.and_then(|item| getter(item, id));
        let new_key = new.and_then(|item| getter(item, id));

        if old_key == new_key {
            return;
        }

        match new_key {
            Some(new_key) => {
                if let Some(old_key) = old_key {
                    self.remove(old_key);
                }
                if let Err((field, _)) = self.map.insert_sync(new_key, *id) {
                    error!(?field, ?id, "index exists");
                }
            }
            None => {
                if let Some(old_key) = old_key {
                    self.remove(old_key);
                }
            }
        }
    }

    fn batch_update(&self, items: &[crate::Entry<Item>]) {
        for item in items {
            let (key, val) = item.split_ref();
            let new_key = (self.getter)(val, key);
            if let Some(new_key) = new_key {
                let res = self.map.insert_sync(new_key, *key);
                if let Err((field, _)) = res {
                    error!(?field, ?key, "index exists");
                }
            }
        }
    }
}
