use std::{
    cell::Cell,
    fmt::Debug,
    marker::PhantomData,
    str::FromStr,
    sync::atomic::{AtomicU8, Ordering},
    time::{Duration, SystemTime},
};

use bytemuck::{Pod, TransparentWrapper, Zeroable};
use rapira::{Rapira, RapiraError};
use rend::u64_be;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};

use crate::{
    GetType, IdStr, ScalarTyp, Typ,
    enc::IdHasher,
    error::ArmourError,
    key_part::{KeyPart, MILLISECOND_BITS},
    key_type::KeyType,
    num_ops::g8bits,
};

type Result<T, E = ArmourError> = core::result::Result<T, E>;

/// different from the unix timestamp epoch in milliseconds
///
/// 2023-04-09 0:00:00 UTC
const TS_DIFF: u64 = 0x187_6350_0000;

const TS_BITS: u32 = 40;
const TS_MAX: u64 = (1 << TS_BITS) - 1;
// 64 - 40 = 24
const TS_SHIFT: u32 = u64::BITS - TS_BITS;

// 2^8 = 256
pub(crate) const SHARD_BITS: u32 = 8;
// 255
pub const SHARD_ID_MAX: u64 = (1 << SHARD_BITS) - 1;

/// 16 bits
const SEQ_BITS: u32 = u64::BITS - TS_BITS - SHARD_BITS;
/// 65_535 max sequence
const SEQ_MAX: u64 = (1 << SEQ_BITS) - 1;

// 2^3 = 8
const SHARD_INST_BITS: u32 = 3;
// 7
pub const SHARD_INSTANCE_ID_MAX: u64 = (1 << SHARD_INST_BITS) - 1;
// 5 + 16 = 21
const SHARD_INSTANCE_SHIFT: u32 = SHARD_THREAD_BITS + SEQ_BITS;

// 2^5 = 32
pub const SHARD_THREAD_BITS: u32 = 5;
// 31
pub const SHARD_THREAD_ID_MAX: u64 = (1 << SHARD_THREAD_BITS) - 1;

#[derive(Debug, Clone, Copy)]
struct SeqForMs {
    /// timestamp in milliseconds
    ts: u64,
    seq: u64,
    count: u64,
}

impl SeqForMs {
    #[cfg(feature = "std")]
    #[inline]
    fn with_ts_and_rand_seq(ts: u64) -> Self {
        use rand::{RngExt, rng};

        let seq = rng().random_range(0..=SEQ_MAX);

        SeqForMs { ts, seq, count: 0 }
    }

    #[cold]
    fn wait_inc(self, ts: u64, duration: Duration) -> Self {
        tracing::info!(?duration, "wait_inc");
        let nanos = duration.subsec_nanos() % 1_000_000;
        let wait_nanos = 1_000_000 - nanos;
        std::thread::sleep(Duration::from_nanos(wait_nanos as u64));

        SeqForMs::with_ts_and_rand_seq(ts + 1)
    }

    #[inline]
    fn increment(mut self, ts: u64, duration: Duration) -> Self {
        if self.count == SEQ_MAX {
            self.wait_inc(ts, duration)
        } else {
            if self.seq == SEQ_MAX {
                self.seq = 0;
            } else {
                self.seq += 1;
            }
            self.count += 1;
            self
        }
    }
}

static THREAD_SEQ: AtomicU8 = AtomicU8::new(0);

thread_local! {
    static SEQ_CHECK: Cell<SeqForMs> = Cell::new(SeqForMs::with_ts_and_rand_seq(0));
    static THREAD_ID: Cell<u8> = Cell::new(THREAD_SEQ.fetch_add(1, Ordering::Relaxed));
}

/// - 64bit id [--timestamp(40)--|--shard_id(8)--|--seq_id(16)--]
/// - 40 bit - timestamp (in milliseconds) started from 2023-04-09 0:00:00 UTC, 34 years available (from 2023 to 2057)
/// - 8 bit - shard_id ([datacenter_id / instance_id] with thread_id), 256 values
/// - 16 bit - random sequence id (unique for thread / per thread), 65_536 values
/// - Blowfish for encryption
/// - ZBASE32 for encoding into 13 length string (example: "ifs1gp9dw8hdw")
/// - create ~ 50ns (~ 21k in 1ms or 21m in 1s) in 1 thread
///
/// Notes:
/// - checks timestamp and shard_id when deserialized from string
/// - Fuid is not inlined for InlineArray
#[derive(Hash, IntoBytes, FromBytes, Immutable, KnownLayout, TransparentWrapper)]
#[cfg_attr(
    feature = "rkyv",
    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
#[cfg_attr(feature = "bitcode", derive(bitcode::Encode, bitcode::Decode))]
#[repr(transparent)]
#[transparent(u64_be)]
pub struct Fuid<H>(pub u64_be, PhantomData<H>);

unsafe impl<T> Zeroable for Fuid<T> {}
unsafe impl<T: 'static> Pod for Fuid<T> {}

impl<T> Clone for Fuid<T> {
    fn clone(&self) -> Self {
        *self
    }
}

impl<T> Copy for Fuid<T> {}

impl<H> Fuid<H> {
    pub fn from_thread() -> Self {
        let thread_id = THREAD_ID.get();
        Self::with_shard(thread_id as u64)
    }

    /// - 3 bit - instance id (datacenter id / shard id) (max 7)
    /// - 5 bit - thread id (max 31)
    /// - instance_id (datacenter) < 8 and thread_id (worker) < 32
    #[cfg(feature = "std")]
    #[inline]
    pub fn with_inst_thread(instance_id: u64, thread_id: u64) -> Self {
        assert!(instance_id <= SHARD_INSTANCE_ID_MAX);
        assert!(thread_id <= SHARD_THREAD_ID_MAX);

        let shard_id = (instance_id << SHARD_THREAD_BITS) | thread_id;
        Self::with_shard(shard_id)
    }

    /// Create a new Fuid.
    /// - shard_id < 256
    #[cfg(feature = "std")]
    pub fn with_shard(shard_id: u64) -> Self {
        assert!(shard_id <= SHARD_ID_MAX);

        let now = SystemTime::now();
        // in ms
        #[allow(clippy::unwrap_used)]
        let duration = now.duration_since(SystemTime::UNIX_EPOCH).unwrap();
        let timestamp = duration.as_millis() as u64;

        let new_epoch_ts = timestamp - TS_DIFF;

        let id = new_epoch_ts << TS_SHIFT;

        let id = id | (shard_id << SEQ_BITS);

        let mut seq_check = SEQ_CHECK.get();

        if seq_check.ts == timestamp {
            seq_check = seq_check.increment(timestamp, duration);
        } else {
            seq_check = SeqForMs::with_ts_and_rand_seq(timestamp);
        }
        SEQ_CHECK.set(seq_check);

        let id = id | seq_check.seq;

        Self(u64_be::from_native(id), PhantomData)
    }

    /// unix timestamp in milliseconds
    #[inline]
    pub fn timestamp(&self) -> u64 {
        let ts = self.0.to_native() >> TS_SHIFT;
        ts + TS_DIFF
    }

    pub fn date(&self) -> time::OffsetDateTime {
        let ts = self.timestamp();
        let ts = ts as i128;
        let ts = ts * 1_000_000;
        let dt = time::OffsetDateTime::from_unix_timestamp_nanos(ts);
        dt.expect("invalid timestamp")
    }

    /// get LE u64
    #[inline]
    pub fn get(&self) -> u64 {
        self.0.to_native()
    }

    #[inline]
    pub fn to_be_bytes(self) -> [u8; 8] {
        zerocopy::transmute!(self)
    }

    #[inline]
    pub fn from_be_bytes(bytes: [u8; 8]) -> Self {
        zerocopy::transmute!(bytes)
    }

    /// to BE bytes
    #[inline]
    pub fn to_bytes(self) -> [u8; 8] {
        zerocopy::transmute!(self)
    }

    /// from BE bytes
    #[inline]
    pub fn from_bytes(bytes: [u8; 8]) -> Self {
        zerocopy::transmute!(bytes)
    }

    /// to BE u64
    #[inline]
    pub fn to_u64(self) -> u64 {
        zerocopy::transmute!(self)
    }

    /// from BE u64
    #[inline]
    pub fn from_u64(bytes: u64) -> Self {
        zerocopy::transmute!(bytes)
    }

    #[inline]
    pub fn to_le_bytes(self) -> [u8; 8] {
        self.0.to_native().to_le_bytes()
    }

    #[inline]
    pub fn instance_id(&self) -> u64 {
        let ts = self.0.to_native() >> SHARD_INSTANCE_SHIFT;
        ts & SHARD_INSTANCE_ID_MAX
    }

    #[inline]
    pub fn thread_id(&self) -> u64 {
        let ts = self.0.to_native() >> SEQ_BITS;
        ts & SHARD_THREAD_ID_MAX
    }

    #[inline]
    pub fn shard_id(&self) -> u64 {
        let ts = self.0.to_native() >> SEQ_BITS;
        ts & SHARD_ID_MAX
    }

    #[inline]
    fn seq(&self) -> u64 {
        let ts = self.0.to_native();
        ts & SEQ_MAX
    }

    /// id: {id:#x}; {id:#b}; {dt}-{instance_id}-{thread_id}-{seq}
    #[cfg(feature = "std")]
    pub fn format<W>(&self, w: &mut W) -> core::result::Result<(), core::fmt::Error>
    where
        W: std::fmt::Write,
    {
        let id = self.0.to_native();
        let dt = self.date();
        let instance_id = self.instance_id();
        let thread_id = self.thread_id();
        let seq = self.seq();
        write!(
            w,
            "id: {id:#x}; {id:#b}; {dt}-{instance_id}-{thread_id}-{seq}"
        )
    }

    /// return prefix for date [--timestamp(5)--|0x00(1)|0x0000(2)]
    /// ms - unix timestamp in milliseconds
    pub fn date_prefix(ms: u64) -> [u8; 8] {
        let ts = (ms - TS_DIFF) << TS_SHIFT;
        ts.to_be_bytes()
    }

    pub fn check(&self) -> core::result::Result<(), ArmourError> {
        let ts = self.0.to_native() >> TS_SHIFT;
        if ts > TS_MAX {
            return Err(ArmourError::IdDecodeError);
        }
        Ok(())
    }

    /// only for iterator with excluded bounds
    pub fn increment(mut self) -> Self {
        self.0 += 1;
        self
    }

    /// group_id for this Fuid (12 days one group)
    #[inline]
    pub fn group_id(&self) -> u32 {
        g8bits(self.0.to_native(), MILLISECOND_BITS)
    }
}

impl<H: IdHasher> Fuid<H> {
    pub fn ser(self) -> IdStr {
        let u: u64 = zerocopy::transmute!(self);
        H::ser(u)
    }

    pub fn deser(id: &str) -> Result<Self> {
        let id = H::deser(id)?;
        Ok(zerocopy::transmute!(id))
    }
}

impl Fuid<()> {
    pub fn ser(self) -> IdStr {
        let u: [u8; 8] = zerocopy::transmute!(self);
        crate::enc::encode(&u)
    }

    pub fn deser(id: &str) -> Result<Self> {
        let id = crate::enc::decode(id)?;
        Ok(zerocopy::transmute!(id))
    }
}

impl<T> PartialOrd for Fuid<T> {
    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl<T> Ord for Fuid<T> {
    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
        self.0.cmp(&other.0)
    }
}

impl<T> PartialEq for Fuid<T> {
    fn eq(&self, other: &Self) -> bool {
        self.0 == other.0
    }
}

impl<T> Eq for Fuid<T> {}

impl<T> PartialEq<[u8; 8]> for Fuid<T> {
    fn eq(&self, other: &[u8; 8]) -> bool {
        let bytes: &[u8; 8] = zerocopy::transmute_ref!(self);
        bytes == other
    }
}

impl<T> PartialEq<Fuid<T>> for [u8; 8] {
    fn eq(&self, other: &Fuid<T>) -> bool {
        let bytes: &[u8; 8] = zerocopy::transmute_ref!(other);
        bytes == self
    }
}

impl<T> PartialEq<Fuid<T>> for Option<Fuid<T>> {
    fn eq(&self, other: &Fuid<T>) -> bool {
        match self {
            Some(id) => id == other,
            None => false,
        }
    }
}

impl<T> PartialEq<Option<Fuid<T>>> for Fuid<T> {
    fn eq(&self, other: &Option<Fuid<T>>) -> bool {
        match other {
            Some(id) => self == id,
            None => false,
        }
    }
}

impl<T> AsRef<Self> for Fuid<T> {
    fn as_ref(&self) -> &Self {
        self
    }
}

impl<T> AsRef<[u8; 8]> for Fuid<T> {
    fn as_ref(&self) -> &[u8; 8] {
        zerocopy::transmute_ref!(self)
    }
}

/// only check if non-zero, not check valid byte mask
impl<H: IdHasher> Rapira for Fuid<H> {
    const STATIC_SIZE: Option<usize> = Some(8);
    const MIN_SIZE: usize = 8;

    #[inline]
    fn size(&self) -> usize {
        8
    }

    #[inline]
    fn check_bytes(slice: &mut &[u8]) -> rapira::Result<()> {
        let bytes: &[u8] = slice.get(..8).ok_or(RapiraError::SliceLen)?;

        const ZERO_BYTES: [u8; 8] = [0u8; 8];

        if bytes == ZERO_BYTES {
            return Err(RapiraError::NonZero);
        }

        *slice = unsafe { slice.get_unchecked(8..) };
        Ok(())
    }

    #[inline]
    fn from_slice(slice: &mut &[u8]) -> rapira::Result<Self>
    where
        Self: Sized,
    {
        let bytes = <[u8; 8]>::from_slice(slice)?;
        let id = Self::from_bytes(bytes);
        Ok(id)
    }

    #[inline]
    fn convert_to_bytes(&self, slice: &mut [u8], cursor: &mut usize) {
        let bytes: &[u8; 8] = zerocopy::transmute_ref!(self);
        bytes.convert_to_bytes(slice, cursor);
    }

    #[inline]
    fn convert_to_bytes_ctx(
        &self,
        slice: &mut [u8],
        cursor: &mut usize,
        flags: rapira::RapiraFlags,
    ) {
        if flags.has(crate::ID_ENC_FLAG) {
            let u: u64 = zerocopy::transmute!(*self);
            let id = H::encrypt(u);
            id.convert_to_bytes(slice, cursor);
        } else {
            self.convert_to_bytes(slice, cursor)
        }
    }

    #[inline]
    fn from_slice_ctx(slice: &mut &[u8], flags: rapira::RapiraFlags) -> rapira::Result<Self>
    where
        Self: Sized,
    {
        if flags.has(crate::ID_ENC_FLAG) {
            let id = u64::from_slice(slice)?;
            let id = H::decrypt(id);
            Ok(zerocopy::transmute!(id))
        } else {
            Self::from_slice(slice)
        }
    }
}

impl Rapira for Fuid<()> {
    const STATIC_SIZE: Option<usize> = Some(8);
    const MIN_SIZE: usize = 8;

    #[inline]
    fn size(&self) -> usize {
        8
    }

    #[inline]
    fn check_bytes(slice: &mut &[u8]) -> rapira::Result<()> {
        let bytes: &[u8] = slice.get(..8).ok_or(RapiraError::SliceLen)?;

        const ZERO_BYTES: [u8; 8] = [0u8; 8];

        if bytes == ZERO_BYTES {
            return Err(RapiraError::NonZero);
        }

        *slice = unsafe { slice.get_unchecked(8..) };
        Ok(())
    }

    #[inline]
    fn from_slice(slice: &mut &[u8]) -> rapira::Result<Self>
    where
        Self: Sized,
    {
        let bytes = <[u8; 8]>::from_slice(slice)?;
        let id = Self::from_bytes(bytes);
        Ok(id)
    }

    #[inline]
    fn convert_to_bytes(&self, slice: &mut [u8], cursor: &mut usize) {
        let bytes: &[u8; 8] = zerocopy::transmute_ref!(self);
        bytes.convert_to_bytes(slice, cursor);
    }
}

impl<H> GetType for Fuid<H> {
    const TYPE: Typ = Typ::Scalar(ScalarTyp::Fuid);
}

impl<H: armour_typ::IdBrand> armour_typ::GetRefs for Fuid<H> {
    fn refs() -> Vec<armour_typ::FieldRef> {
        vec![armour_typ::FieldRef::leaf(H::BRAND)]
    }
    fn visit_ids(&self, f: &mut dyn FnMut(&'static str, u64)) {
        f(H::BRAND, self.get());
    }
}

impl<H: armour_typ::IdBrand> armour_typ::GetRefs for OptFuid<H> {
    fn refs() -> Vec<armour_typ::FieldRef> {
        vec![armour_typ::FieldRef::leaf(H::BRAND)]
    }
    fn visit_ids(&self, f: &mut dyn FnMut(&'static str, u64)) {
        if let Some(id) = self.get() {
            f(H::BRAND, id.get());
        }
    }
}

impl<H> KeyPart for Fuid<H> {
    const TY: KeyType = KeyType::Fuid;
    const PREFIX_BITS: u32 = MILLISECOND_BITS;
}

impl<H: IdHasher> std::fmt::Display for Fuid<H> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.ser())
    }
}

impl std::fmt::Display for Fuid<()> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.ser())
    }
}

impl<H> Debug for Fuid<H> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let id = self.0.to_native();
        let id = format!("{id:#X}");
        f.debug_tuple("Fuid").field(&id).finish()
    }
}

impl<T: IdHasher> TryFrom<&str> for Fuid<T> {
    type Error = ArmourError;
    fn try_from(val: &str) -> Result<Self> {
        Self::deser(val)
    }
}

impl TryFrom<&str> for Fuid<()> {
    type Error = ArmourError;
    fn try_from(val: &str) -> Result<Self> {
        Self::deser(val)
    }
}

impl<T: IdHasher> FromStr for Fuid<T> {
    type Err = ArmourError;
    fn from_str(s: &str) -> Result<Self> {
        Self::deser(s)
    }
}

impl FromStr for Fuid<()> {
    type Err = ArmourError;
    fn from_str(s: &str) -> Result<Self> {
        Self::deser(s)
    }
}

#[cfg(feature = "std")]
impl<T: IdHasher> Serialize for Fuid<T> {
    fn serialize<S: Serializer>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error> {
        let s = self.ser();
        serializer.serialize_str(&s)
    }
}

#[cfg(feature = "std")]
impl<'de, T: IdHasher> Deserialize<'de> for Fuid<T> {
    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        use serde::de::Error;

        let s: &str = Deserialize::deserialize(deserializer)?;
        let a = Fuid::<T>::deser(s).map_err(|err| {
            tracing::error!("id value error: {err}");
            D::Error::custom("id value error")
        })?;
        Ok(a)
    }
}

#[cfg(feature = "std")]
impl Serialize for Fuid<()> {
    fn serialize<S: Serializer>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error> {
        let s = self.ser();
        serializer.serialize_str(&s)
    }
}

#[cfg(feature = "std")]
impl<'de> Deserialize<'de> for Fuid<()> {
    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        use serde::de::Error;

        let s: &str = Deserialize::deserialize(deserializer)?;
        let a = Fuid::<()>::deser(s).map_err(|err| {
            tracing::error!("id value error: {err}");
            D::Error::custom("id value error")
        })?;
        Ok(a)
    }
}

#[cfg(feature = "ts-rs")]
impl<H> ts_rs::TS for Fuid<H> {
    type WithoutGenerics = Fuid<()>;
    type OptionInnerType = Self;
    fn name(_: &ts_rs::Config) -> String {
        "Fuid".to_owned()
    }
    fn decl_concrete(c: &ts_rs::Config) -> String {
        format!("type Fuid = {};", Self::inline(c))
    }
    fn decl(c: &ts_rs::Config) -> String {
        let inline = <Fuid<()> as ::ts_rs::TS>::inline(c);
        format!("type Fuid = {inline};")
    }
    fn inline(_: &ts_rs::Config) -> String {
        "string".to_owned()
    }
    fn inline_flattened(c: &ts_rs::Config) -> String {
        panic!("{} cannot be flattened", Self::name(c))
    }
    fn output_path() -> Option<std::path::PathBuf> {
        Some(std::path::PathBuf::from("fuid.ts"))
    }
}

#[cfg(feature = "facet")]
unsafe impl<'facet, H: 'static> facet::Facet<'facet> for Fuid<H> {
    const SHAPE: &'static facet::Shape = &const {
        const VTABLE: facet::VTableDirect = facet::vtable_direct!(Fuid<()> =>
            Debug,
            Hash,
            PartialEq,
            PartialOrd,
            Ord,
        );

        facet::ShapeBuilder::for_sized::<Fuid<H>>("Fuid")
            .ty(facet::Type::User(facet::UserType::Struct(facet::StructType {
                repr: facet::Repr::transparent(),
                kind: facet::StructKind::TupleStruct,
                fields: &const {
                    [facet::FieldBuilder::new("0", facet::shape_of::<u64>, 0).build()]
                },
            })))
            .inner(<u64 as facet::Facet>::SHAPE)
            .def(facet::Def::Scalar)
            .vtable_direct(&VTABLE)
            .eq()
            .copy()
            .send()
            .sync()
            .build()
    };
}

#[cfg(feature = "fake")]
impl<H, T> fake::Dummy<T> for Fuid<H> {
    fn dummy_with_rng<R: rand::Rng + ?Sized>(_: &T, _: &mut R) -> Self {
        Fuid::from_thread()
    }
}

// ---------------------------------------------------------------------------
// OptFuid
// ---------------------------------------------------------------------------

/// Optional [`Fuid`] with the same 8-byte representation.
///
/// Zero (`[0u8; 8]`) is interpreted as `None`. Use in zerocopy-compatible
/// structs where `Option<Fuid>` would break alignment (16+ bytes).
///
/// ```ignore
/// #[derive(FromBytes, IntoBytes, Immutable, KnownLayout, Copy, Clone)]
/// #[repr(C)]
/// struct Follower {
///     follow: Fuid<H>,       // 8 bytes
///     follower: OptFuid<H>,  // 8 bytes, zero = None
/// }
/// ```
#[derive(IntoBytes, FromBytes, Immutable, KnownLayout, TransparentWrapper)]
#[cfg_attr(
    feature = "rkyv",
    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
#[cfg_attr(feature = "bitcode", derive(bitcode::Encode, bitcode::Decode))]
#[repr(transparent)]
#[transparent(u64_be)]
pub struct OptFuid<H>(u64_be, PhantomData<H>);

unsafe impl<T> Zeroable for OptFuid<T> {}
unsafe impl<T: 'static> Pod for OptFuid<T> {}

impl<T> Clone for OptFuid<T> {
    fn clone(&self) -> Self {
        *self
    }
}

impl<T> Copy for OptFuid<T> {}

impl<H> OptFuid<H> {
    // SAFETY: OptFuid is Zeroable — all-zero bytes is valid and means None.
    pub const NONE: Self = unsafe { core::mem::zeroed() };

    #[inline]
    pub fn some(id: Fuid<H>) -> Self {
        Self(id.0, PhantomData)
    }

    #[inline]
    pub fn get(self) -> Option<Fuid<H>> {
        if self.0.to_native() == 0 {
            None
        } else {
            Some(Fuid(self.0, PhantomData))
        }
    }

    #[inline]
    pub fn is_some(self) -> bool {
        self.0.to_native() != 0
    }

    #[inline]
    pub fn is_none(self) -> bool {
        self.0.to_native() == 0
    }

    /// get raw LE u64 (0 for None)
    #[inline]
    pub fn get_raw(self) -> u64 {
        self.0.to_native()
    }

    /// to BE bytes
    #[inline]
    pub fn to_bytes(self) -> [u8; 8] {
        zerocopy::transmute!(self)
    }

    /// from BE bytes
    #[inline]
    pub fn from_bytes(bytes: [u8; 8]) -> Self {
        zerocopy::transmute!(bytes)
    }

    #[inline]
    pub fn to_be_bytes(self) -> [u8; 8] {
        zerocopy::transmute!(self)
    }

    #[inline]
    pub fn from_be_bytes(bytes: [u8; 8]) -> Self {
        zerocopy::transmute!(bytes)
    }
}

impl<T> Default for OptFuid<T> {
    fn default() -> Self {
        Self::NONE
    }
}

impl<H> From<Fuid<H>> for OptFuid<H> {
    fn from(id: Fuid<H>) -> Self {
        Self::some(id)
    }
}

impl<H> From<Option<Fuid<H>>> for OptFuid<H> {
    fn from(opt: Option<Fuid<H>>) -> Self {
        match opt {
            Some(id) => Self::some(id),
            None => Self::NONE,
        }
    }
}

impl<H> From<OptFuid<H>> for Option<Fuid<H>> {
    fn from(opt: OptFuid<H>) -> Self {
        opt.get()
    }
}

impl<T> PartialEq for OptFuid<T> {
    fn eq(&self, other: &Self) -> bool {
        self.0 == other.0
    }
}

impl<T> Eq for OptFuid<T> {}

impl<T> PartialOrd for OptFuid<T> {
    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl<T> Ord for OptFuid<T> {
    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
        self.0.cmp(&other.0)
    }
}

impl<T> core::hash::Hash for OptFuid<T> {
    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
        self.0.hash(state);
    }
}

impl<T> PartialEq<Fuid<T>> for OptFuid<T> {
    fn eq(&self, other: &Fuid<T>) -> bool {
        self.0 == other.0
    }
}

impl<T> PartialEq<OptFuid<T>> for Fuid<T> {
    fn eq(&self, other: &OptFuid<T>) -> bool {
        self.0 == other.0
    }
}

impl<T> AsRef<[u8; 8]> for OptFuid<T> {
    fn as_ref(&self) -> &[u8; 8] {
        zerocopy::transmute_ref!(self)
    }
}

impl<H> Debug for OptFuid<H> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self.0.to_native() {
            0 => write!(f, "OptFuid(None)"),
            id => {
                let id = format!("{id:#X}");
                f.debug_tuple("OptFuid").field(&id).finish()
            }
        }
    }
}

impl<H: IdHasher> Rapira for OptFuid<H> {
    const STATIC_SIZE: Option<usize> = Some(8);
    const MIN_SIZE: usize = 8;

    #[inline]
    fn size(&self) -> usize {
        8
    }

    #[inline]
    fn check_bytes(slice: &mut &[u8]) -> rapira::Result<()> {
        if slice.len() < 8 {
            return Err(RapiraError::SliceLen);
        }
        *slice = unsafe { slice.get_unchecked(8..) };
        Ok(())
    }

    #[inline]
    fn from_slice(slice: &mut &[u8]) -> rapira::Result<Self>
    where
        Self: Sized,
    {
        let bytes = <[u8; 8]>::from_slice(slice)?;
        Ok(Self::from_bytes(bytes))
    }

    #[inline]
    fn convert_to_bytes(&self, slice: &mut [u8], cursor: &mut usize) {
        let bytes: &[u8; 8] = zerocopy::transmute_ref!(self);
        bytes.convert_to_bytes(slice, cursor);
    }

    #[inline]
    fn convert_to_bytes_ctx(
        &self,
        slice: &mut [u8],
        cursor: &mut usize,
        flags: rapira::RapiraFlags,
    ) {
        if flags.has(crate::ID_ENC_FLAG) && self.is_some() {
            let u: u64 = zerocopy::transmute!(*self);
            let id = H::encrypt(u);
            id.convert_to_bytes(slice, cursor);
        } else {
            self.convert_to_bytes(slice, cursor)
        }
    }

    #[inline]
    fn from_slice_ctx(slice: &mut &[u8], flags: rapira::RapiraFlags) -> rapira::Result<Self>
    where
        Self: Sized,
    {
        if flags.has(crate::ID_ENC_FLAG) {
            let bytes = <[u8; 8]>::from_slice(slice)?;
            if bytes == [0u8; 8] {
                Ok(Self::NONE)
            } else {
                let id = u64::from_le_bytes(bytes);
                let id = H::decrypt(id);
                Ok(zerocopy::transmute!(id))
            }
        } else {
            Self::from_slice(slice)
        }
    }
}

impl Rapira for OptFuid<()> {
    const STATIC_SIZE: Option<usize> = Some(8);
    const MIN_SIZE: usize = 8;

    #[inline]
    fn size(&self) -> usize {
        8
    }

    #[inline]
    fn check_bytes(slice: &mut &[u8]) -> rapira::Result<()> {
        if slice.len() < 8 {
            return Err(RapiraError::SliceLen);
        }
        *slice = unsafe { slice.get_unchecked(8..) };
        Ok(())
    }

    #[inline]
    fn from_slice(slice: &mut &[u8]) -> rapira::Result<Self>
    where
        Self: Sized,
    {
        let bytes = <[u8; 8]>::from_slice(slice)?;
        Ok(Self::from_bytes(bytes))
    }

    #[inline]
    fn convert_to_bytes(&self, slice: &mut [u8], cursor: &mut usize) {
        let bytes: &[u8; 8] = zerocopy::transmute_ref!(self);
        bytes.convert_to_bytes(slice, cursor);
    }
}

impl<H> GetType for OptFuid<H> {
    const TYPE: Typ = Typ::Scalar(ScalarTyp::Fuid);
}

#[cfg(feature = "std")]
impl<H: IdHasher> Serialize for OptFuid<H> {
    fn serialize<S: Serializer>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error> {
        match self.get() {
            Some(id) => serializer.serialize_str(&id.ser()),
            None => serializer.serialize_none(),
        }
    }
}

#[cfg(feature = "std")]
impl<'de, H: IdHasher> Deserialize<'de> for OptFuid<H> {
    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        use serde::de::Error;
        let s: Option<&str> = Deserialize::deserialize(deserializer)?;
        match s {
            Some(s) => {
                let id = Fuid::<H>::deser(s).map_err(|err| {
                    tracing::error!("id value error: {err}");
                    D::Error::custom("id value error")
                })?;
                Ok(Self::some(id))
            }
            None => Ok(Self::NONE),
        }
    }
}

#[cfg(feature = "std")]
impl Serialize for OptFuid<()> {
    fn serialize<S: Serializer>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error> {
        match self.get() {
            Some(id) => serializer.serialize_str(&id.ser()),
            None => serializer.serialize_none(),
        }
    }
}

#[cfg(feature = "std")]
impl<'de> Deserialize<'de> for OptFuid<()> {
    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        use serde::de::Error;
        let s: Option<&str> = Deserialize::deserialize(deserializer)?;
        match s {
            Some(s) => {
                let id = Fuid::<()>::deser(s).map_err(|err| {
                    tracing::error!("id value error: {err}");
                    D::Error::custom("id value error")
                })?;
                Ok(Self::some(id))
            }
            None => Ok(Self::NONE),
        }
    }
}

#[cfg(feature = "ts-rs")]
impl<H> ts_rs::TS for OptFuid<H> {
    type WithoutGenerics = OptFuid<()>;
    type OptionInnerType = Self;
    fn name(_: &ts_rs::Config) -> String {
        "OptFuid".to_owned()
    }
    fn decl_concrete(c: &ts_rs::Config) -> String {
        format!("type OptFuid = {};", Self::inline(c))
    }
    fn decl(c: &ts_rs::Config) -> String {
        let inline = <OptFuid<()> as ::ts_rs::TS>::inline(c);
        format!("type OptFuid = {inline};")
    }
    fn inline(_: &ts_rs::Config) -> String {
        "string | null".to_owned()
    }
    fn inline_flattened(c: &ts_rs::Config) -> String {
        panic!("{} cannot be flattened", Self::name(c))
    }
    fn output_path() -> Option<std::path::PathBuf> {
        Some(std::path::PathBuf::from("opt_fuid.ts"))
    }
}

#[cfg(feature = "facet")]
unsafe impl<'facet, H: 'static> facet::Facet<'facet> for OptFuid<H> {
    const SHAPE: &'static facet::Shape = &const {
        const VTABLE: facet::VTableDirect = facet::vtable_direct!(OptFuid<()> =>
            Debug,
            Hash,
            PartialEq,
            PartialOrd,
            Ord,
        );

        facet::ShapeBuilder::for_sized::<OptFuid<H>>("OptFuid")
            .ty(facet::Type::User(facet::UserType::Struct(facet::StructType {
                repr: facet::Repr::transparent(),
                kind: facet::StructKind::TupleStruct,
                fields: &const {
                    [facet::FieldBuilder::new("0", facet::shape_of::<u64>, 0).build()]
                },
            })))
            .inner(<u64 as facet::Facet>::SHAPE)
            .def(facet::Def::Scalar)
            .vtable_direct(&VTABLE)
            .eq()
            .copy()
            .send()
            .sync()
            .build()
    };
}

#[cfg(feature = "fake")]
impl<H, T> fake::Dummy<T> for OptFuid<H> {
    fn dummy_with_rng<R: rand::Rng + ?Sized>(_: &T, _: &mut R) -> Self {
        Self::some(Fuid::from_thread())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::enc::Cipher;

    #[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)]
    pub struct Hasher;

    impl IdHasher for Hasher {
        const HASHER: Cipher = Cipher::new(
            "_mKbKGF2IrkGvIJvl97HuCgWjgt6QRZ7Ye8DHBQ2anvyi18BdMz8uN6Ej3YJApooY6qDu0obqq4",
        );
    }

    #[test]
    fn test_fuid_creation() {
        let fuid = Fuid::<Hasher>::with_inst_thread(1, 2);
        assert_eq!(fuid.instance_id(), 1);
        assert_eq!(fuid.thread_id(), 2);

        let fuid = Fuid::<Hasher>::with_shard(55);
        assert_eq!(fuid.shard_id(), 55);
    }

    #[test]
    fn test_fuid_ser_deser() {
        let fuid = Fuid::<Hasher>::with_shard(111);
        let id_str = fuid.ser();
        #[allow(clippy::unwrap_used)]
        let deserialized_fuid = Fuid::<Hasher>::deser(&id_str).unwrap();
        assert_eq!(fuid, deserialized_fuid);
    }

    // -- OptFuid tests --

    #[test]
    fn test_opt_fuid_none() {
        let opt = OptFuid::<Hasher>::NONE;
        assert!(opt.is_none());
        assert!(!opt.is_some());
        assert_eq!(opt.get(), None);
        assert_eq!(opt.to_bytes(), [0u8; 8]);
    }

    #[test]
    fn test_opt_fuid_some() {
        let fuid = Fuid::<Hasher>::with_shard(1);
        let opt = OptFuid::some(fuid);
        assert!(opt.is_some());
        assert!(!opt.is_none());
        assert_eq!(opt.get(), Some(fuid));
    }

    #[test]
    fn test_opt_fuid_conversions() {
        let fuid = Fuid::<Hasher>::with_shard(1);

        let opt: OptFuid<Hasher> = fuid.into();
        assert_eq!(opt.get(), Some(fuid));

        let opt: OptFuid<Hasher> = None.into();
        assert!(opt.is_none());

        let opt: OptFuid<Hasher> = Some(fuid).into();
        let back: Option<Fuid<Hasher>> = opt.into();
        assert_eq!(back, Some(fuid));
    }

    #[test]
    fn test_opt_fuid_size() {
        assert_eq!(size_of::<OptFuid<Hasher>>(), 8);
        assert_eq!(size_of::<OptFuid<Hasher>>(), size_of::<Fuid<Hasher>>());
    }

    #[test]
    fn test_opt_fuid_default() {
        let opt = OptFuid::<Hasher>::default();
        assert!(opt.is_none());
    }

    #[test]
    fn test_opt_fuid_eq_with_fuid() {
        let fuid = Fuid::<Hasher>::with_shard(1);
        let opt = OptFuid::some(fuid);
        assert_eq!(opt, fuid);
        assert_eq!(fuid, opt);
    }

    // -- Rapira _ctx round-trip tests --

    #[test]
    fn test_fuid_ctx_roundtrip() {
        let id = Fuid::<Hasher>::with_shard(1);
        let flags = rapira::RapiraFlags::new(crate::ID_ENC_FLAG);

        let encrypted = rapira::serialize_ctx(&id, flags);
        let plain = rapira::serialize(&id);
        assert_ne!(encrypted, plain);

        let decoded: Fuid<Hasher> = rapira::deserialize_ctx(&encrypted, flags).unwrap();
        assert_eq!(decoded, id);
    }

    #[test]
    fn test_fuid_ctx_no_flag_same_as_plain() {
        let id = Fuid::<Hasher>::with_shard(1);
        let plain = rapira::serialize(&id);
        let ctx_none = rapira::serialize_ctx(&id, rapira::RapiraFlags::NONE);
        assert_eq!(plain, ctx_none);
    }

    #[test]
    fn test_opt_fuid_ctx_none_roundtrip() {
        let opt = OptFuid::<Hasher>::NONE;
        let flags = rapira::RapiraFlags::new(crate::ID_ENC_FLAG);
        let bytes = rapira::serialize_ctx(&opt, flags);
        assert_eq!(bytes, [0u8; 8]);
        let decoded: OptFuid<Hasher> = rapira::deserialize_ctx(&bytes, flags).unwrap();
        assert!(decoded.is_none());
    }

    #[test]
    fn test_opt_fuid_ctx_some_roundtrip() {
        let id = Fuid::<Hasher>::with_shard(1);
        let opt = OptFuid::some(id);
        let flags = rapira::RapiraFlags::new(crate::ID_ENC_FLAG);

        let encrypted = rapira::serialize_ctx(&opt, flags);
        let plain = rapira::serialize(&opt);
        assert_ne!(encrypted, plain);

        let decoded: OptFuid<Hasher> = rapira::deserialize_ctx(&encrypted, flags).unwrap();
        assert_eq!(decoded.get(), Some(id));
    }
}

#[cfg(test)]
mod refs_tests {
    use armour_typ::{FieldRef, GetRefs, IdBrand};
    use rend::u64_be;

    use super::*;

    crate::const_hasher_or!(
        test_refs_id,
        "TREFS",
        "K_zxJmNfxOS4R5DfaA2Dl42iHC4LjNaPTfLha6VKQoU1xtk91gdYVSNyl_o5iw1jqxx2zTqYqrQ"
    );

    fn fuid_from_native(v: u64) -> Fuid<test_refs_id::Hasher> {
        Fuid(u64_be::from_native(v), PhantomData)
    }

    #[test]
    fn hasher_macro_emits_brand() {
        assert_eq!(<test_refs_id::Hasher as IdBrand>::BRAND, "TREFS");
    }

    #[test]
    fn fuid_get_refs() {
        let id = fuid_from_native(0xDEAD_BEEF);
        let refs = Fuid::<test_refs_id::Hasher>::refs();
        assert_eq!(refs, vec![FieldRef::leaf("TREFS")]);

        let mut seen = Vec::new();
        id.visit_ids(&mut |b, v| seen.push((b, v)));
        assert_eq!(seen, vec![("TREFS", 0xDEAD_BEEF)]);
    }

    #[test]
    fn opt_fuid_none_not_visited() {
        let none = OptFuid::<test_refs_id::Hasher>::NONE;
        let mut seen = Vec::new();
        none.visit_ids(&mut |b, v| seen.push((b, v)));
        assert!(seen.is_empty());

        let some = OptFuid::some(fuid_from_native(5));
        some.visit_ids(&mut |b, v| seen.push((b, v)));
        assert_eq!(seen, vec![("TREFS", 5)]);
    }
}
