use std::{borrow::Borrow, fmt::Debug, hash::Hash, sync::Arc};

use scc::HashIndex;

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

pub type HashIndexExtractor<Item, Id, HashValue> = fn(&Item, &Id) -> Option<HashValue>;

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

impl<Key, Item> CompositionIndex<Key, Item>
where
    Item: Record,
    Item::SelfId: Copy,
    Key: Hash + Eq + Clone + 'static,
{
    pub fn new(capacity: usize, getter: HashIndexExtractor<Item, Item::SelfId, Key>) -> Self {
        let map = HashIndex::with_capacity(capacity.max(1024));
        let map = Arc::new(map);
        Self { map, getter }
    }
}

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

    pub fn exist<Q>(&self, field: &Q) -> bool
    where
        Key: Borrow<Q>,
        Q: Hash + 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 CompositionIndex<Key, Item>
where
    Key: Sync + Send + Eq + Hash + 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>]) {
        let reserved = self
            .map
            .reserve(items.len())
            .expect("items too large for indexing");

        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 = reserved.insert_sync(new_key, *key);
                if let Err((field, _)) = res {
                    error!(?field, ?key, "index exists");
                }
            }
        }
    }
}
