use candid::Principal;

const CANISTER_ID_HASH_LEN_IN_BYTES: usize = 10;
const HASH_LEN_IN_BYTES: usize = 28;
const TYPE_SELF_AUTH: u8 = 0x02;

pub fn is_canister(id: &Principal) -> bool {
    let blob = id.as_slice();
    blob.len() == CANISTER_ID_HASH_LEN_IN_BYTES
}

pub fn is_user_principal(id: &Principal) -> bool {
    let blob = id.as_slice();
    if blob.len() != HASH_LEN_IN_BYTES + 1 {
        return false;
    }
    if blob.last() != Some(&TYPE_SELF_AUTH) {
        return false;
    }
    true
}
#[macro_export]
macro_rules! new_type_principal {
    // is_canister is true or false
    ($name:ident, $is_canister:expr) => {
        #[derive(
            CandidType,
            Clone,
            Copy,
            Debug,
            PartialEq,
            Eq,
            Hash,
            PartialOrd,
            Ord,
            Deserialize,
            Serialize,
        )]
        #[serde(transparent)]
        pub struct $name(Principal);

        impl From<Principal> for $name {
            fn from(principal: Principal) -> Self {
                Self::new(principal)
            }
        }

        impl From<$name> for Principal {
            fn from(t: $name) -> Self {
                t.0
            }
        }

        impl $name {
            pub fn new(p: Principal) -> Self {
                if $is_canister {
                    if p != Principal::management_canister() {
                        assert!(
                            is_canister(&p),
                            "{}: principal is not a canister",
                            stringify!($name)
                        );
                    }
                } else {
                    assert!(
                        is_user_principal(&p),
                        "{}: principal is not a user principal",
                        stringify!($name)
                    );
                }
                Self(p)
            }

            pub fn as_slice(&self) -> &[u8] {
                self.0.as_slice()
            }

            pub fn from_slice(bytes: &[u8]) -> Self {
                Self(Principal::from_slice(bytes))
            }

            pub fn as_ref(&self) -> &Principal {
                &self.0
            }

            pub fn to_text(&self) -> String {
                self.0.to_text()
            }

            pub fn from_text(text: &str) -> Result<Self, String> {
                Principal::from_text(text)
                    .map(Self::new)
                    .map_err(|e| format!("{}: {:?}", stringify!($name), e))
            }
        }

        impl Storable for $name {
            fn to_bytes(&self) -> std::borrow::Cow<[u8]> {
                self.0.as_ref().into()
            }

            fn from_bytes(bytes: std::borrow::Cow<[u8]>) -> Self {
                Self(Principal::from_slice(bytes.as_ref()))
            }
            const BOUND: Bound = Bound::Bounded {
                max_size: 29,
                is_fixed_size: false,
            };
        }

        impl PartialEq<Principal> for $name {
            fn eq(&self, other: &Principal) -> bool {
                self.0 == *other
            }
        }
        
        impl Display for $name {
            fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
                write!(f, "{}", self.0.to_text())
            }
        }
    };
}
