use std::hash::Hash;

use armour_core::{
    CollectionShape, Fuid, GetRefs, GetType, Id64, IdBrand, StorageBackend, StorageClass,
    ValueModel,
};

use crate::armour::collection::Collection;
use crate::key::Key;
use crate::{
    Codec, CollectionMeta, DbResult, TypedMap, TypedTree, TypedWriteHook, ZeroMap, ZeroTree,
};

/// Write/delete in an index collection from a generated hook.
pub trait IndexTarget: SchemaCollection {
    fn put_entry(&self, k: &Self::K, v: &Self::V) -> DbResult<()>;
    fn delete_entry(&self, k: &Self::K) -> DbResult<()>;
}

/// Key that is a branded ID: round-trips through u64.
pub trait BrandedKey: Key {
    const BRAND: &'static str;
    fn from_id(id: u64) -> Self;
    fn id_value(&self) -> u64;
}

impl<H: IdBrand> BrandedKey for Fuid<H>
where
    Fuid<H>: Key,
{
    const BRAND: &'static str = H::BRAND;
    fn from_id(id: u64) -> Self {
        Fuid::from_be_bytes(id.to_be_bytes())
    }
    fn id_value(&self) -> u64 {
        self.get()
    }
}

impl<T: IdBrand> BrandedKey for Id64<T>
where
    Id64<T>: Key,
{
    const BRAND: &'static str = T::BRAND;
    fn from_id(id: u64) -> Self {
        Id64::from_be_bytes(id.to_be_bytes())
    }
    fn id_value(&self) -> u64 {
        self.get()
    }
}

/// Typed collection access for the schema registry and validator.
/// Each impl knows how to scan and check key presence.
pub trait SchemaCollection: Collection + Send + Sync + 'static {
    type K: Key + GetRefs + GetType + 'static;
    type V: CollectionMeta + GetRefs + 'static;

    /// Physical storage class of this collection.
    const STORAGE: StorageClass;

    fn scan(&self, f: &mut dyn FnMut(&Self::K, &Self::V));
    fn contains_key(&self, key: &Self::K) -> bool;
    /// false — no record; true — record exists and `f` was called with the value.
    fn with_value(&self, key: &Self::K, f: &mut dyn FnMut(&Self::V)) -> bool;
}

impl<T, C, H> SchemaCollection for TypedTree<T::SelfId, T, C, H>
where
    T: CollectionMeta + GetRefs + Clone + Send + Sync + 'static,
    T::SelfId: Key + GetRefs + GetType + Ord + Send + Sync + 'static,
    C: Codec<T> + Default + Send + Sync + 'static,
    H: TypedWriteHook<T::SelfId, T> + 'static,
{
    type K = T::SelfId;
    type V = T;
    const STORAGE: StorageClass = StorageClass {
        model: ValueModel::Typed,
        shape: CollectionShape::Tree,
        backend: StorageBackend::Bitcask,
    };

    fn scan(&self, f: &mut dyn FnMut(&Self::K, &Self::V)) {
        for (k, v) in self.iter() {
            f(&k, v);
        }
    }

    fn contains_key(&self, key: &Self::K) -> bool {
        self.get(key).is_some()
    }

    fn with_value(&self, key: &Self::K, f: &mut dyn FnMut(&Self::V)) -> bool {
        match self.get(key) {
            Some(v) => {
                f(&*v);
                true
            }
            None => false,
        }
    }
}

impl<T, C, H> IndexTarget for TypedTree<T::SelfId, T, C, H>
where
    T: CollectionMeta + GetRefs + Clone + Send + Sync + 'static,
    T::SelfId: Key + GetRefs + GetType + Ord + Send + Sync + 'static,
    C: Codec<T> + Default + Send + Sync + 'static,
    H: TypedWriteHook<T::SelfId, T> + 'static,
{
    fn put_entry(&self, k: &Self::K, v: &Self::V) -> DbResult<()> {
        self.put(k, v.clone()).map(drop)
    }

    fn delete_entry(&self, k: &Self::K) -> DbResult<()> {
        self.delete(k).map(drop)
    }
}

impl<T, C, H> SchemaCollection for TypedMap<T::SelfId, T, C, H>
where
    T: CollectionMeta + GetRefs + Clone + Send + Sync + 'static,
    T::SelfId: Key + GetRefs + GetType + Hash + Eq + Send + Sync + 'static,
    C: Codec<T> + Default + Send + Sync + 'static,
    H: TypedWriteHook<T::SelfId, T> + 'static,
{
    type K = T::SelfId;
    type V = T;
    const STORAGE: StorageClass = StorageClass {
        model: ValueModel::Typed,
        shape: CollectionShape::Map,
        backend: StorageBackend::Bitcask,
    };

    fn scan(&self, f: &mut dyn FnMut(&Self::K, &Self::V)) {
        self.for_each(|k, v| f(k, v));
    }

    fn contains_key(&self, key: &Self::K) -> bool {
        self.contains(key)
    }

    fn with_value(&self, key: &Self::K, f: &mut dyn FnMut(&Self::V)) -> bool {
        match self.get(key) {
            Some(v) => {
                f(&*v);
                true
            }
            None => false,
        }
    }
}

impl<T, C, H> IndexTarget for TypedMap<T::SelfId, T, C, H>
where
    T: CollectionMeta + GetRefs + Clone + Send + Sync + 'static,
    T::SelfId: Key + GetRefs + GetType + Hash + Eq + Send + Sync + 'static,
    C: Codec<T> + Default + Send + Sync + 'static,
    H: TypedWriteHook<T::SelfId, T> + 'static,
{
    fn put_entry(&self, k: &Self::K, v: &Self::V) -> DbResult<()> {
        self.put(k, v.clone()).map(drop)
    }

    fn delete_entry(&self, k: &Self::K) -> DbResult<()> {
        self.delete(k).map(drop)
    }
}

/// `SchemaCollection`/`IndexTarget` for Zero collections are identical across
/// durability backends, so the impl pair is stamped per backend by a macro.
/// (A single `D: Durability` impl is avoided on purpose: method impl blocks in
/// zero_tree.rs/zero_map.rs are split per backend, and the macro expands to
/// exactly the code that those blocks provide.)
macro_rules! zero_tree_schema_impls {
    ($dur:ty, $backend:expr $(,)?) => {
        impl<T, const V: usize, H> SchemaCollection for ZeroTree<T::SelfId, V, T, H, $dur>
        where
            T: CollectionMeta
                + GetRefs
                + Copy
                + zerocopy::IntoBytes
                + zerocopy::FromBytes
                + zerocopy::Immutable
                + Send
                + Sync
                + 'static,
            T::SelfId: Key + GetRefs + GetType + Ord + Send + Sync + 'static,
            H: TypedWriteHook<T::SelfId, T> + 'static,
        {
            type K = T::SelfId;
            type V = T;
            const STORAGE: StorageClass = StorageClass {
                model: ValueModel::Zero,
                shape: CollectionShape::Tree,
                backend: $backend,
            };

            fn scan(&self, f: &mut dyn FnMut(&Self::K, &Self::V)) {
                for (k, v) in self.iter() {
                    f(&k, &v);
                }
            }

            fn contains_key(&self, key: &Self::K) -> bool {
                self.contains(key)
            }

            fn with_value(&self, key: &Self::K, f: &mut dyn FnMut(&Self::V)) -> bool {
                match self.get(key) {
                    Some(v) => {
                        f(&v);
                        true
                    }
                    None => false,
                }
            }
        }

        impl<T, const V: usize, H> IndexTarget for ZeroTree<T::SelfId, V, T, H, $dur>
        where
            T: CollectionMeta
                + GetRefs
                + Copy
                + zerocopy::IntoBytes
                + zerocopy::FromBytes
                + zerocopy::Immutable
                + Send
                + Sync
                + 'static,
            T::SelfId: Key + GetRefs + GetType + Ord + Send + Sync + 'static,
            H: TypedWriteHook<T::SelfId, T> + 'static,
        {
            fn put_entry(&self, k: &Self::K, v: &Self::V) -> DbResult<()> {
                self.put(k, v).map(drop)
            }

            fn delete_entry(&self, k: &Self::K) -> DbResult<()> {
                self.delete(k).map(drop)
            }
        }
    };
}

zero_tree_schema_impls!(crate::durability::Bitcask, StorageBackend::Bitcask);
zero_tree_schema_impls!(crate::durability::Fixed, StorageBackend::Fixed);

macro_rules! zero_map_schema_impls {
    ($dur:ty, $backend:expr $(,)?) => {
        impl<T, const V: usize, H> SchemaCollection for ZeroMap<T::SelfId, V, T, H, $dur>
        where
            T: CollectionMeta
                + GetRefs
                + Copy
                + zerocopy::IntoBytes
                + zerocopy::FromBytes
                + zerocopy::Immutable
                + Send
                + Sync
                + 'static,
            T::SelfId: Key + GetRefs + GetType + Hash + Eq + Send + Sync + 'static,
            H: TypedWriteHook<T::SelfId, T> + 'static,
        {
            type K = T::SelfId;
            type V = T;
            const STORAGE: StorageClass = StorageClass {
                model: ValueModel::Zero,
                shape: CollectionShape::Map,
                backend: $backend,
            };

            fn scan(&self, f: &mut dyn FnMut(&Self::K, &Self::V)) {
                self.for_each(|k, v| f(k, v));
            }

            fn contains_key(&self, key: &Self::K) -> bool {
                self.contains(key)
            }

            fn with_value(&self, key: &Self::K, f: &mut dyn FnMut(&Self::V)) -> bool {
                match self.get(key) {
                    Some(v) => {
                        f(&v);
                        true
                    }
                    None => false,
                }
            }
        }

        impl<T, const V: usize, H> IndexTarget for ZeroMap<T::SelfId, V, T, H, $dur>
        where
            T: CollectionMeta
                + GetRefs
                + Copy
                + zerocopy::IntoBytes
                + zerocopy::FromBytes
                + zerocopy::Immutable
                + Send
                + Sync
                + 'static,
            T::SelfId: Key + GetRefs + GetType + Hash + Eq + Send + Sync + 'static,
            H: TypedWriteHook<T::SelfId, T> + 'static,
        {
            fn put_entry(&self, k: &Self::K, v: &Self::V) -> DbResult<()> {
                self.put(k, v).map(drop)
            }

            fn delete_entry(&self, k: &Self::K) -> DbResult<()> {
                self.delete(k).map(drop)
            }
        }
    };
}

zero_map_schema_impls!(crate::durability::Bitcask, StorageBackend::Bitcask);
zero_map_schema_impls!(crate::durability::Fixed, StorageBackend::Fixed);

// ---------------------------------------------------------------------------
// Var collections (typed). Raw VarTree/VarMap store untyped ByteView values
// and carry no GetRefs, so they are intentionally not registerable.
//
// Gated on `var-collections`; `schema` already implies `typed-tree` via
// `armour`, so VarTypedTree/VarTypedMap exist whenever this block compiles.
//
// Unlike the Typed/Zero impls, `scan` decodes the raw inner bytes itself (it
// does NOT reuse the lossy `iter()`/`for_each`) so that an undecodable row is
// surfaced as a `warn` here, instead of being silently swallowed at `debug`.
// ---------------------------------------------------------------------------
#[cfg(feature = "var-collections")]
use crate::{VarTypedMap, VarTypedTree};

#[cfg(feature = "var-collections")]
impl<T, C, H> SchemaCollection for VarTypedTree<T::SelfId, T, C, H>
where
    T: CollectionMeta + GetRefs + Send + Sync + 'static,
    T::SelfId: Key + GetRefs + GetType + Ord + Send + Sync + 'static,
    C: Codec<T> + Clone + Send + Sync + 'static,
    H: TypedWriteHook<T::SelfId, T> + 'static,
{
    type K = T::SelfId;
    type V = T;
    const STORAGE: StorageClass = StorageClass {
        model: ValueModel::VarTyped,
        shape: CollectionShape::Tree,
        backend: StorageBackend::Bitcask,
    };

    fn scan(&self, f: &mut dyn FnMut(&Self::K, &Self::V)) {
        let codec = self.codec();
        for (k, bytes) in self.as_inner().iter() {
            match codec.decode_from(&bytes) {
                Ok(v) => f(&k, &v),
                Err(_) => tracing::warn!(
                    collection = T::NAME,
                    "schema scan: decode error in VarTypedTree, skipping entry — relations not checked"
                ),
            }
        }
    }

    fn contains_key(&self, key: &Self::K) -> bool {
        self.contains(key)
    }

    fn with_value(&self, key: &Self::K, f: &mut dyn FnMut(&Self::V)) -> bool {
        match self.get(key) {
            Some(v) => {
                f(&v);
                true
            }
            None => false,
        }
    }
}

#[cfg(feature = "var-collections")]
impl<T, C, H> IndexTarget for VarTypedTree<T::SelfId, T, C, H>
where
    T: CollectionMeta + GetRefs + Send + Sync + 'static,
    T::SelfId: Key + GetRefs + GetType + Ord + Send + Sync + 'static,
    C: Codec<T> + Clone + Send + Sync + 'static,
    H: TypedWriteHook<T::SelfId, T> + 'static,
{
    fn put_entry(&self, k: &Self::K, v: &Self::V) -> DbResult<()> {
        self.put(k, v).map(drop)
    }

    fn delete_entry(&self, k: &Self::K) -> DbResult<()> {
        self.delete(k).map(drop)
    }
}

#[cfg(feature = "var-collections")]
impl<T, C, H> SchemaCollection for VarTypedMap<T::SelfId, T, C, H>
where
    T: CollectionMeta + GetRefs + Send + Sync + 'static,
    T::SelfId: Key + GetRefs + GetType + Hash + Eq + Send + Sync + 'static,
    C: Codec<T> + Clone + Send + Sync + 'static,
    H: TypedWriteHook<T::SelfId, T> + 'static,
{
    type K = T::SelfId;
    type V = T;
    const STORAGE: StorageClass = StorageClass {
        model: ValueModel::VarTyped,
        shape: CollectionShape::Map,
        backend: StorageBackend::Bitcask,
    };

    fn scan(&self, f: &mut dyn FnMut(&Self::K, &Self::V)) {
        let codec = self.codec();
        self.as_inner()
            .for_each(|k, bytes| match codec.decode_from(&bytes) {
                Ok(v) => f(&k, &v),
                Err(_) => tracing::warn!(
                    collection = T::NAME,
                    "schema scan: decode error in VarTypedMap, skipping entry — relations not checked"
                ),
            });
    }

    fn contains_key(&self, key: &Self::K) -> bool {
        self.contains(key)
    }

    fn with_value(&self, key: &Self::K, f: &mut dyn FnMut(&Self::V)) -> bool {
        match self.get(key) {
            Some(v) => {
                f(&v);
                true
            }
            None => false,
        }
    }
}

#[cfg(feature = "var-collections")]
impl<T, C, H> IndexTarget for VarTypedMap<T::SelfId, T, C, H>
where
    T: CollectionMeta + GetRefs + Send + Sync + 'static,
    T::SelfId: Key + GetRefs + GetType + Hash + Eq + Send + Sync + 'static,
    C: Codec<T> + Clone + Send + Sync + 'static,
    H: TypedWriteHook<T::SelfId, T> + 'static,
{
    fn put_entry(&self, k: &Self::K, v: &Self::V) -> DbResult<()> {
        self.put(k, v).map(drop)
    }

    fn delete_entry(&self, k: &Self::K) -> DbResult<()> {
        self.delete(k).map(drop)
    }
}
