use std::{
    ops::Bound,
    sync::{
        Arc,
        atomic::{AtomicU64, Ordering},
    },
};

use async_broadcast::{Receiver, broadcast};
use derive_more::Debug;
use xxhash_rust::xxh3::Xxh3Default;

use super::{
    ByteValue, KeyValue,
    db::Database,
    error::{DbError, DbResult},
    replication::{ChangeEvent, ReplicationEvent, ReplicationEventHandler, Subscribers},
};
use crate::{
    types::{ArmourError, attribute::EntityAttribute, num_ops::g4bits},
    utils::{CheckSumVec, CollectionInfo, GroupVal, HashPoints},
};

#[derive(Debug)]
pub(crate) struct InnerFields {
    pub(crate) info: CollectionInfo,
    /// map of groups hashes
    pub(crate) hashpoints: HashPoints,
    /// approximate number of elements in the collection
    pub(crate) seq: AtomicU64,
    #[debug("{}", self.replication_handler.is_some())]
    pub(crate) replication_handler: ReplicationEventHandler,
}

impl InnerFields {
    pub(crate) fn invalidate_hash(&self, group_id: u32) {
        self.hashpoints.insert(
            group_id,
            GroupVal {
                hash: 0,
                changed: true,
            },
        );
    }
}

#[derive(Clone, Debug)]
pub struct RawTree {
    pub(crate) db: Database,
    /// custom name (with_name fn) or Record::NAME
    pub name: String,
    pub attributes: &'static EntityAttribute,
    #[debug(skip)]
    pub(crate) tree: super::Tree,
    pub(crate) removed: super::Tree,
    pub(crate) inner: Arc<InnerFields>,
    pub(crate) subscribers: Subscribers,
}

impl RawTree {
    #[tracing::instrument(level = "info", skip(self), fields(name = self.name, ret))]
    pub fn checksum(&self) -> u32 {
        let checksum = self.tree.checksum().expect("cannot calculate checksum");
        if checksum != 0 {
            tracing::debug!("checksum: {checksum:#X}");
        }

        checksum
    }

    pub fn is_empty(&self) -> bool {
        self.tree.is_empty().expect("cannot check if tree is empty")
    }

    #[tracing::instrument(level = "trace", skip(self), fields(name = self.name), ret)]
    pub fn count(&self) -> u64 {
        self.inner.seq.load(Ordering::Relaxed)
    }

    #[tracing::instrument(level = "info", skip(self), fields(name = self.name))]
    pub fn subscribe(&self) -> Receiver<ChangeEvent> {
        // let subscriber = self.tree.watch_prefix([]);
        // let f = futures::stream::unfold(subscriber, |mut sub| async move {
        //     (&mut sub).await.map(|event| {
        //         let event_batch: Box<dyn EventBatch<Bytes = ByteValue>> =
        //             Box::new(SledEvent(event));
        //         (event_batch, sub)
        //     })
        // });
        // f.boxed_local()

        let (_, r) = self.subscribers.get_or_init(|| {
            let (mut s, r) = broadcast(1024);
            let r = r.deactivate();
            s.set_await_active(false);
            s.set_overflow(true);
            (s, r)
        });
        r.activate_cloned()
    }

    #[tracing::instrument(level = "info", skip(self), fields(name = self.name, ret))]
    pub fn hashpoints(&self) -> CheckSumVec {
        self.inner
            .hashpoints
            .iter()
            .map(|entry| {
                let key = *entry.key();
                (key, entry.value().hash)
            })
            .collect()
    }

    #[tracing::instrument(level = "trace", skip(self), fields(name = self.name))]
    pub fn scan_group(&self, group: u32) -> impl DoubleEndedIterator<Item = KeyValue> + use<> {
        // for ID: 0x0
        // for ID: 0x0
        // for Fuid: 0x0 - April 09 2023 10:13:00 CET?
        let start = group.to_be_bytes();

        // for ID: 24 bits
        // for Id: 32 bits
        // for Fuid: 10 bits
        // for LowId: 8 bits
        let group_bits = self.attributes.group_bits;
        // for ID: 32 - 24 = 8 bits
        // for Id: 32 - 32 = 0 bits
        // for Fuid: 32 - 10 = 22 bits
        let bits_sub = u32::BITS - group_bits;
        // for ID: 2^8 = 256 = 0x100
        // for Id: 2^0 = 1 = 0x1
        // for Fuid: 2^22 = 4_194_304 = 0x40_0000
        let bits_pow_of_two = 2u32.pow(bits_sub);
        // for ID: 0x0 + 2^8 = 0x100
        // for Id: 0x0 + 2^0 = 0x1
        // for Fuid: 0x0 + 2^22 = 0x40_0000
        let end = group + bits_pow_of_two;
        let end = end.to_be_bytes();

        let start = Bound::Included(start.as_ref());
        let end = Bound::Excluded(end.as_ref());
        self.tree
            .range::<&[u8], _>((start, end))
            .filter_map(|item| match item {
                Ok((key, value)) => {
                    // ...
                    Some((key, value))
                }
                Err(e) => {
                    tracing::error!(%e);
                    None
                }
            })
    }

    #[tracing::instrument(level = "info", skip(self), fields(name = self.name, ret))]
    pub fn recalcucate_hash(&self) -> u64 {
        let hash = self
            .inner
            .hashpoints
            .iter()
            .map(|item| {
                let group_val = item.value();

                if group_val.changed {
                    let group = *item.key();
                    drop(item);
                    let mut hash_val = Xxh3Default::new();

                    for (key, value) in self.scan_group(group) {
                        hash_val.update(&key);
                        hash_val.update(&value);
                    }
                    let hash = hash_val.digest();
                    // TODO: deadlock? race condition?
                    self.inner.hashpoints.insert(
                        group,
                        GroupVal {
                            hash,
                            changed: false,
                        },
                    );
                    hash
                } else {
                    item.value().hash
                }
            })
            .fold(Xxh3Default::new(), |mut hasher, item| {
                hasher.update(&item.to_le_bytes());
                hasher
            });

        hash.digest()
    }

    /// save seq number, type hash, version to db, flush tree
    #[tracing::instrument(name = "tree_close", skip(self), fields(name = self.name))]
    pub fn close(&self) {
        let seq = self.inner.seq.load(Ordering::Relaxed);

        if seq != 0 {
            tracing::debug!(seq, "close seq");
        }

        let typ_hash = self.attributes.ty.h();
        let version = self.attributes.version;

        let info = CollectionInfo {
            seq,
            typ_hash,
            version,
        };
        self.db
            .db_info
            .data
            .lock()
            .collections
            .insert(self.name.clone(), info);

        let count = Arc::strong_count(&self.inner);

        if count != 1 {
            tracing::error!(count, "strong refs");
        }
    }

    /// return slice with [key + value] bytes
    #[tracing::instrument(level = "trace", skip(self), fields(name = self.name, ret))]
    pub fn get(&self, id: &[u8]) -> DbResult<Option<Vec<u8>>> {
        self.tree
            .get(id)
            .map(|item| {
                item.map(|item| {
                    let len = id.len() + item.len();
                    let mut v = vec![0; len];
                    v[..id.len()].copy_from_slice(id);
                    v[id.len()..].copy_from_slice(&item);
                    v
                })
            })
            .map_err(DbError::from)
    }

    #[tracing::instrument(level = "trace", skip(self), fields(name = self.name))]
    pub fn range(
        &self,
        range: (Bound<&[u8]>, Bound<&[u8]>),
    ) -> Box<dyn DoubleEndedIterator<Item = KeyValue>> {
        let iter = self.tree.range::<&[u8], _>(range);
        let iter = iter.filter_map(|item| match item {
            Ok((key, value)) => Some((key, value)),
            Err(e) => {
                tracing::error!(%e);
                None
            }
        });
        Box::new(iter)
    }

    #[tracing::instrument(level = "trace", skip(self), fields(name = self.name))]
    fn invalidate_hash(&self, id: &[u8]) {
        let mut bytes = [0; 4];
        bytes.copy_from_slice(&id[..4]);
        let group = u32::from_be_bytes(bytes);
        let group = g4bits(group, self.attributes.group_bits);
        self.inner.invalidate_hash(group);
    }

    #[tracing::instrument(level = "debug", skip(self), fields(name = self.name), err, ret)]
    pub fn next_id(&self) -> DbResult<u64> {
        const NEXT_ID: &str = "next_id";
        let next_id_key = format!("{NEXT_ID}-{}", self.name);

        let key_ref = next_id_key.as_bytes();
        let db = self.db.db.lock();
        let mut current = db.get(key_ref)?;

        loop {
            let tmp = current.as_ref().map(AsRef::as_ref);

            let mut id = 1u64;

            let next = match tmp {
                Some(bytes) => {
                    let bytes = bytes
                        .try_into()
                        .map_err(|err| DbError::Armour(ArmourError::from(err)))?;
                    let old = u64::from_le_bytes(bytes);
                    id = old + 1;
                    id.to_le_bytes().to_vec()
                }
                None => id.to_le_bytes().to_vec(),
            };

            match db.compare_and_swap(key_ref, tmp, Some(next))? {
                Ok(_) => return Ok(id),
                Err(sled::CompareAndSwapError { current: cur, .. }) => {
                    current = cur;
                }
            }
        }
    }

    #[tracing::instrument(level = "debug", skip(self, event), fields(name = self.name, event = event.variant()), err)]
    pub fn apply_event(&self, event: ChangeEvent) -> DbResult<()> {
        match &event {
            ChangeEvent::Upsert((key, val)) => {
                let old = self.tree.insert(key, val.clone())?;
                if old.is_none() {
                    self.inner.seq.fetch_add(1, Ordering::Relaxed);
                }
                if let Some(f) = self.inner.replication_handler.as_ref() {
                    f(ReplicationEvent::Upsert {
                        key,
                        val,
                        old_val: old.as_ref(),
                    });
                }
            }
            ChangeEvent::Delete(key) => {
                match self.tree.remove(key)? {
                    Some(val) => {
                        self.inner.seq.fetch_sub(1, Ordering::AcqRel);
                        if let Some(f) = self.inner.replication_handler.as_ref() {
                            f(ReplicationEvent::Delete { key, val: &val });
                        }
                    }
                    _ => {
                        tracing::error!(?key, "delete not found");
                    }
                };
            }
            ChangeEvent::ChangeId(old, new) => {
                let val = self.tree.remove(old)?.ok_or(DbError::NotFound)?;
                if self.tree.insert(new, val.clone())?.is_some() {
                    tracing::error!(?new, "change_id already exists");
                };
                if let Some(f) = self.inner.replication_handler.as_ref() {
                    f(ReplicationEvent::IdChange {
                        old_key: old,
                        new_key: new,
                        val: &val,
                    });
                }
                self.invalidate_hash(new);
            }
        }
        self.invalidate_hash(event.key());
        if let Some((sender, _)) = self.subscribers.get() {
            match sender.try_broadcast(event) {
                Ok(None) => {}
                Ok(Some(item)) => {
                    tracing::error!(?item, "broadcast is full");
                }
                Err(e) => {
                    tracing::error!(%e);
                }
            }
        }
        Ok(())
    }

    #[tracing::instrument(level = "info", skip(self, iter), fields(name = self.name), err)]
    pub fn apply_batch(
        &self,
        iter: impl Iterator<Item = (ByteValue, Option<ByteValue>)>,
    ) -> DbResult<()> {
        let mut batch = sled::Batch::default();

        // TODO
        for (key, val) in iter {
            self.invalidate_hash(&key);

            match &val {
                Some(val) => {
                    batch.insert(key.clone(), val.clone());

                    if let Some(f) = self.inner.replication_handler.as_ref() {
                        let old = self.tree.get(&key)?;
                        f(ReplicationEvent::Upsert {
                            key: &key,
                            val,
                            old_val: old.as_ref(),
                        });
                    }
                }
                None => {
                    batch.remove(key.clone());
                    if let Some(f) = self.inner.replication_handler.as_ref() {
                        match self.tree.get(&key)? {
                            Some(item) => {
                                f(ReplicationEvent::Delete {
                                    key: &key,
                                    val: &item,
                                });
                            }
                            _ => {
                                tracing::error!(?key, "delete not found");
                            }
                        }
                    }
                }
            }
            if let Some((sender, _)) = self.subscribers.get() {
                let msg = ChangeEvent::from_kv(key, val);
                match sender.try_broadcast(msg) {
                    Ok(None) => {}
                    Ok(Some(item)) => {
                        tracing::error!(?item, "broadcast is full");
                    }
                    Err(e) => {
                        tracing::error!(%e);
                    }
                }
            }
        }

        self.tree.apply_batch(batch)?;

        Ok(())
    }

    #[doc(hidden)]
    pub fn inner(&self) -> &super::Tree {
        &self.tree
    }
}
