use core::num::NonZeroU32;
use std::ops::Range;

use rapira::FromU8;
use serde::{Serialize, Serializer, ser::SerializeStruct};
use zerocopy::{Immutable, IntoBytes, KnownLayout, TryFromBytes};

use crate::{
    KeyScheme, KeyType, RecordStatus,
    cid::Cid,
    types::{ArmourError, Result, enc::IdHasher, ident::ID, num_ops::g14},
};

type FiveByte = [u8; 5];

/// if status is 255, then id is deleted
#[derive(
    Debug,
    PartialOrd,
    Ord,
    PartialEq,
    Eq,
    Clone,
    Copy,
    IntoBytes,
    TryFromBytes,
    Immutable,
    KnownLayout,
)]
#[cfg_attr(
    feature = "rkyv",
    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
#[cfg_attr(feature = "ts-rs", ts(concrete(I = ())))]
#[cfg_attr(feature = "ts-rs", ts(export, export_to = "uid.ts"))]
#[repr(C)]
pub struct Uid<I, S = RecordStatus> {
    pub status: S,
    id: ID<I>,
}

impl<I, T> Uid<I, T> {
    /// [0, 0, 0, 0, 0]
    pub const ACTIVE_START: FiveByte = [RecordStatus::ACTIVE, 0, 0, 0, 0];
    /// [255, 0, 0, 0, 0]
    pub const DELETED_END: FiveByte = [RecordStatus::DELETED, 0, 0, 0, 0];
    pub const NOT_DELETED_RANGE: Range<FiveByte> = Self::ACTIVE_START..Self::DELETED_END;
    /// ( [0, 0, 0, 0, 0] .. [255, 0, 0, 0, 0] )
    pub const NOT_DELETED_RANGE_SLICE: Range<&'static [u8; 5]> =
        &Self::ACTIVE_START..&Self::DELETED_END;

    #[inline]
    pub fn new(id: ID<I>) -> Uid<I, T>
    where
        T: Default,
    {
        Uid {
            status: Default::default(),
            id,
        }
    }

    #[inline]
    pub fn with_status(id: ID<I>, status: T) -> Uid<I, T> {
        Uid { status, id }
    }

    #[inline]
    pub fn copy_with_status(mut self, status: T) -> Self {
        self.status = status;
        self
    }

    #[inline]
    pub fn get_u32(&self) -> u32 {
        self.id.get()
    }

    #[inline]
    pub fn get_id(&self) -> ID<I> {
        self.id
    }

    #[inline]
    pub fn get_status(&self) -> T
    where
        T: Copy,
    {
        self.status
    }

    pub fn set_status(&mut self, status: T) {
        self.status = status;
    }

    #[inline]
    pub fn is_deleted(&self) -> bool
    where
        T: Copy,
        u8: From<T>,
    {
        u8::from(self.status) == RecordStatus::DELETED
    }

    /// get [u8; 4] prefix
    pub fn group_prefix(&self) -> [u8; 4]
    where
        Self: IntoBytes + Immutable,
    {
        let mut bytes = [0; 4];
        bytes.copy_from_slice(&IntoBytes::as_bytes(self)[..4]);
        bytes
    }
}

impl<I, T> From<Uid<I, T>> for u32 {
    fn from(val: Uid<I, T>) -> u32 {
        val.get_u32()
    }
}

impl<I, T> PartialEq<ID<I>> for Uid<I, T> {
    fn eq(&self, other: &ID<I>) -> bool {
        self.id.eq(other)
    }
}

impl<I, T: Default> TryFrom<u32> for Uid<I, T> {
    type Error = ArmourError;
    fn try_from(id: u32) -> Result<Self> {
        let id = NonZeroU32::new(id).ok_or(ArmourError::NonZeroError)?;
        let id = ID::new(id);

        Ok(Self::new(id))
    }
}

impl<T: Serialize, I: IdHasher> Serialize for Uid<I, T> {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        let id = self.id.ser();
        let mut s = serializer.serialize_struct("Uid", 2)?;
        s.serialize_field("status", &self.status)?;
        s.serialize_field("id", &id)?;
        s.end()
    }
}

impl<I, T> Cid for Uid<I, T>
where
    I: Ord,
    T: Ord + Copy + FromU8 + TryFrom<u8>,
    ArmourError: From<<T as TryFrom<u8>>::Error>,
    u8: From<T>,
{
    type B = FiveByte;
    const TY: KeyScheme = KeyScheme::Typed(&[KeyType::U8, KeyType::U32]);
    const GROUP_BITS: u32 = 32;

    #[inline]
    fn encode(&self) -> FiveByte {
        let id_vec: [u8; 4] = self.id.to_le_bytes();
        let status = u8::from(self.status);
        [status, id_vec[3], id_vec[2], id_vec[1], id_vec[0]]
    }

    fn decode(bytes: &FiveByte) -> Result<Self> {
        let first_byte = bytes[0];
        let status: T = T::try_from(first_byte)?;
        let id_bytes: [u8; 4] = [bytes[4], bytes[3], bytes[2], bytes[1]];
        let id = NonZeroU32::new(u32::from_le_bytes(id_bytes)).ok_or(ArmourError::NonZeroError)?;
        let id = ID::new(id);
        Ok(Uid { status, id })
    }

    fn from_bytes(bytes: &[u8]) -> Result<Self> {
        let bytes = <FiveByte>::try_from(bytes)?;
        Self::decode(&bytes)
    }

    fn group_id(&self) -> u32 {
        let status = u8::from(self.status);
        let id = self.id.get();
        g14(status, id)
    }
}

#[cfg(feature = "ts")]
impl<I, T> TypeDef for Uid<I, T>
where
    T: 'static + TypeDef,
    I: 'static,
{
    const INFO: TypeInfo = TypeInfo::Defined(DefinedTypeInfo {
        def: TypeDefinition {
            docs: None,
            path: &[],
            name: Ident("Uid"),
            generic_vars: &[],
            def: TypeExpr::Object(TypeObject {
                docs: None,
                index_signature: None,
                fields: &[
                    ObjectField {
                        docs: None,
                        name: TypeString {
                            docs: None,
                            value: "id",
                        },
                        optional: false,
                        r#type: TypeExpr::Ref(&<ID<T>>::INFO),
                    },
                    ObjectField {
                        docs: None,
                        name: TypeString {
                            docs: None,
                            value: "status",
                        },
                        optional: false,
                        r#type: TypeExpr::Ref(&T::INFO),
                    },
                ],
            }),
        },
        generic_args: &[],
    });
}

#[cfg(feature = "ts")]
impl<T> Type for Id<T> {
    const NAME: &'static str = "Id";
    fn inline(opts: DefOpts, generics: &[DataType]) -> DataType {
        DataType::Object(ObjectType {
            name: "Id".to_owned(),
            generics: Vec::new(),
            fields: vec![
                ObjectField {
                    name: "status".to_owned(),
                    ty: RecordStatus::inline(
                        DefOpts {
                            parent_inline: false,
                            type_map: opts.type_map,
                        },
                        generics,
                    ),
                    optional: false,
                    flatten: false,
                },
                ObjectField {
                    name: "id".to_owned(),
                    ty: ID::<T>::inline(
                        DefOpts {
                            parent_inline: false,
                            type_map: opts.type_map,
                        },
                        generics,
                    ),
                    optional: false,
                    flatten: false,
                },
            ],
            tag: None,
            type_id: None,
        })
    }
}
