use core::{hash::Hash, str::FromStr};
use std::{fmt::Debug, marker::PhantomData};

#[cfg(feature = "bytemuck")]
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 super::{ArmourError, IdStr, enc::IdHasher, num_ops::g8bits};
use crate::{
    Cid, GetType, KeyScheme, KeyType, Typ, cid::SEQ64_BITS, types::Result, types::key_part::KeyPart,
};

/// - 64bit sequence id
/// - Blowfish for encryption (64bit block)
/// - ZBASE32 for encoding into 13 length string
#[derive(IntoBytes, FromBytes, Immutable, KnownLayout)]
#[cfg_attr(
    feature = "rkyv",
    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
#[cfg_attr(feature = "bytemuck", derive(TransparentWrapper))]
#[cfg_attr(feature = "bytemuck", transparent(u64_be))]
#[cfg_attr(feature = "bitcode", derive(bitcode::Encode, bitcode::Decode))]
#[repr(transparent)]
pub struct Id64<T>(pub u64_be, PhantomData<T>);

#[cfg(feature = "bytemuck")]
unsafe impl<T> Zeroable for Id64<T> {}
#[cfg(feature = "bytemuck")]
unsafe impl<T: 'static> Pod for Id64<T> {}

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

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

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

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

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

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

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

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

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

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

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

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

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

    /// Create a new ID64 from a native (LE) u64.
    #[inline]
    pub fn new(id: u64) -> Self {
        Id64(u64_be::from_native(id), PhantomData)
    }

    #[inline]
    pub fn to_le_bytes(self) -> [u8; 8] {
        self.0.to_native().to_le_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)
    }

    /// 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)
    }

    /// get native (LE) u32
    #[inline]
    pub fn get_u32(self) -> Option<u32> {
        let native = self.0.to_native();
        if native > u32::MAX as u64 {
            None
        } else {
            Some(native as u32)
        }
    }

    #[inline]
    pub fn increment(mut self) -> Self {
        self.0 += 1;
        self
    }
}

impl<T: IdHasher> Id64<T> {
    #[inline]
    pub fn deser(s: &str) -> Result<Id64<T>> {
        let v = T::deser(s)?;
        Ok(zerocopy::transmute!(v))
    }

    #[inline]
    pub fn ser(self) -> IdStr {
        let u: u64 = zerocopy::transmute!(self);
        T::ser(u)
    }
}

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

impl TryFrom<&str> for Id64<()> {
    type Error = ArmourError;
    fn try_from(val: &str) -> Result<Self, Self::Error> {
        let val = u64::from_str(val)?;
        Ok(Self(u64_be::from_native(val), PhantomData))
    }
}

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

impl FromStr for Id64<()> {
    type Err = ArmourError;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let val = u64::from_str(s)?;
        Ok(Self(u64_be::from_native(val), PhantomData))
    }
}

impl<T> From<Id64<T>> for u64 {
    #[inline(always)]
    fn from(id: Id64<T>) -> Self {
        id.get()
    }
}

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

impl<T> Debug for Id64<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_tuple("ID").field(&self.0).finish()
    }
}

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

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

#[cfg(feature = "std")]
impl<'de, T: IdHasher> Deserialize<'de> for Id64<T> {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        use serde::de::Error;
        let s: &str = Deserialize::deserialize(deserializer)?;
        let a = Id64::<T>::deser(s).map_err(|_| D::Error::custom("id value error"))?;
        Ok(a)
    }
}

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

#[cfg(feature = "std")]
impl<'de> Deserialize<'de> for Id64<()> {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s: u64 = Deserialize::deserialize(deserializer)?;
        Ok(Id64(u64_be::from_native(s), PhantomData))
    }
}

impl<T> Rapira for Id64<T> {
    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]) -> Result<(), rapira::RapiraError>
    where
        Self: Sized,
    {
        let bytes: &[u8] = slice.get(..8).ok_or(RapiraError::SliceLen)?;

        if bytes == [0u8; 8] {
            return Err(RapiraError::NonZero);
        }

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

    #[inline]
    fn from_slice(slice: &mut &[u8]) -> Result<Self, rapira::RapiraError>
    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<T> GetType for Id64<T> {
    const TYPE: Typ = Typ::Id64;
}

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

impl<T> Cid for Id64<T> {
    type B = [u8; 8];
    const TY: KeyScheme = KeyScheme::Typed(&[KeyType::U64]);
    const GROUP_BITS: u32 = SEQ64_BITS;

    #[inline]
    fn encode(&self) -> Self::B {
        zerocopy::transmute!(*self)
    }

    #[inline]
    fn encode_owned(self) -> Self::B {
        zerocopy::transmute!(self)
    }

    #[inline]
    fn from_bytes(bytes: &[u8]) -> Result<Self> {
        let bytes: &[u8; 8] = bytes
            .get(..8)
            .ok_or(ArmourError::BytesLenError)?
            .try_into()?;

        Self::decode(bytes)
    }

    #[inline]
    fn decode(bytes: &Self::B) -> Result<Self> {
        Ok(zerocopy::transmute!(*bytes))
    }

    #[inline]
    fn decode_owned(bytes: Self::B) -> Result<Self> {
        Ok(zerocopy::transmute!(bytes))
    }

    /// 16 bits prefix for id in big endian (one group is 65536 elements)
    #[inline]
    fn group_id(&self) -> u32 {
        let id = self.get();
        g8bits(id, Self::GROUP_BITS)
    }
}

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

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

        let u = (0..100_000_000).fake::<u64>();
        Self::new(u)
    }
}

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

    #[test]
    fn test_increment_basic() {
        let id = Id64::<()>::new(42);
        let incremented = id.increment();
        assert_eq!(incremented.get(), 43);
    }

    #[test]
    fn test_increment_multiple() {
        let id = Id64::<()>::new(100);
        let id = id.increment();
        assert_eq!(id.get(), 101);
        let id = id.increment();
        assert_eq!(id.get(), 102);
        let id = id.increment();
        assert_eq!(id.get(), 103);
    }

    #[test]
    fn test_increment_zero() {
        let id = Id64::<()>::new(0);
        let incremented = id.increment();
        assert_eq!(incremented.get(), 1);
    }

    #[test]
    fn test_increment_large_value() {
        let id = Id64::<()>::new(u64::MAX - 1);
        let incremented = id.increment();
        assert_eq!(incremented.get(), u64::MAX);
    }

    #[test]
    #[cfg(debug_assertions)]
    #[should_panic(expected = "attempt to add with overflow")]
    fn test_increment_overflow_debug() {
        let id = Id64::<()>::new(u64::MAX);
        let _ = id.increment(); // This will panic in debug mode
    }

    #[test]
    #[cfg(not(debug_assertions))]
    fn test_increment_overflow_release() {
        let id = Id64::<()>::new(u64::MAX);
        let incremented = id.increment();
        // In release mode, this will wrap around to 0
        assert_eq!(incremented.get(), 0);
    }

    #[test]
    fn test_increment_endianness() {
        // Test that increment works correctly with big-endian representation
        let id = Id64::<()>::new(255); // 0xFF
        let incremented = id.increment();
        assert_eq!(incremented.get(), 256); // 0x100

        // Verify the big-endian bytes are correct
        let bytes = incremented.to_be_bytes();
        assert_eq!(bytes, [0, 0, 0, 0, 0, 0, 1, 0]);
    }
}
