#![allow(clippy::field_reassign_with_default)]
use alloc::{format, string::String, vec::Vec};
use core::{
    array::TryFromSliceError,
    convert::TryFrom,
    fmt::{self, Debug, Display, Formatter},
    num::ParseIntError,
};
#[cfg(feature = "datasize")]
use datasize::DataSize;
use rand::{
    distributions::{Distribution, Standard},
    Rng,
};
#[cfg(feature = "json-schema")]
use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema};
use serde::{de::Error as SerdeError, Deserialize, Deserializer, Serialize, Serializer};
use crate::{
    bytesrepr,
    bytesrepr::{Error, FromBytes},
    checksummed_hex, AccessRights, ApiError, Key, ACCESS_RIGHTS_SERIALIZED_LENGTH,
};
pub const UREF_ADDR_LENGTH: usize = 32;
pub const UREF_SERIALIZED_LENGTH: usize = UREF_ADDR_LENGTH + ACCESS_RIGHTS_SERIALIZED_LENGTH;
pub(super) const UREF_FORMATTED_STRING_PREFIX: &str = "uref-";
pub type URefAddr = [u8; UREF_ADDR_LENGTH];
#[derive(Debug)]
#[non_exhaustive]
pub enum FromStrError {
    InvalidPrefix,
    MissingSuffix,
    InvalidAccessRights,
    Hex(base16::DecodeError),
    Int(ParseIntError),
    Address(TryFromSliceError),
}
impl From<base16::DecodeError> for FromStrError {
    fn from(error: base16::DecodeError) -> Self {
        FromStrError::Hex(error)
    }
}
impl From<ParseIntError> for FromStrError {
    fn from(error: ParseIntError) -> Self {
        FromStrError::Int(error)
    }
}
impl From<TryFromSliceError> for FromStrError {
    fn from(error: TryFromSliceError) -> Self {
        FromStrError::Address(error)
    }
}
impl Display for FromStrError {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        match self {
            FromStrError::InvalidPrefix => write!(f, "prefix is not 'uref-'"),
            FromStrError::MissingSuffix => write!(f, "no access rights as suffix"),
            FromStrError::InvalidAccessRights => write!(f, "invalid access rights"),
            FromStrError::Hex(error) => {
                write!(f, "failed to decode address portion from hex: {}", error)
            }
            FromStrError::Int(error) => write!(f, "failed to parse an int: {}", error),
            FromStrError::Address(error) => {
                write!(f, "address portion is the wrong length: {}", error)
            }
        }
    }
}
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Default)]
#[cfg_attr(feature = "datasize", derive(DataSize))]
pub struct URef(URefAddr, AccessRights);
impl URef {
    pub const fn new(address: URefAddr, access_rights: AccessRights) -> Self {
        URef(address, access_rights)
    }
    pub fn addr(&self) -> URefAddr {
        self.0
    }
    pub fn access_rights(&self) -> AccessRights {
        self.1
    }
    #[must_use]
    pub fn with_access_rights(self, access_rights: AccessRights) -> Self {
        URef(self.0, access_rights)
    }
    #[must_use]
    pub fn remove_access_rights(self) -> Self {
        URef(self.0, AccessRights::NONE)
    }
    #[must_use]
    pub fn is_readable(self) -> bool {
        self.1.is_readable()
    }
    #[must_use]
    pub fn into_read(self) -> URef {
        URef(self.0, AccessRights::READ)
    }
    #[must_use]
    pub fn into_write(self) -> URef {
        URef(self.0, AccessRights::WRITE)
    }
    #[must_use]
    pub fn into_add(self) -> URef {
        URef(self.0, AccessRights::ADD)
    }
    #[must_use]
    pub fn into_read_add_write(self) -> URef {
        URef(self.0, AccessRights::READ_ADD_WRITE)
    }
    #[must_use]
    pub fn into_read_write(self) -> URef {
        URef(self.0, AccessRights::READ_WRITE)
    }
    pub fn is_writeable(self) -> bool {
        self.1.is_writeable()
    }
    pub fn is_addable(self) -> bool {
        self.1.is_addable()
    }
    pub fn to_formatted_string(self) -> String {
        let access_rights_bits = self.access_rights().bits();
        format!(
            "{}{}-{:03o}",
            UREF_FORMATTED_STRING_PREFIX,
            base16::encode_lower(&self.addr()),
            access_rights_bits
        )
    }
    pub fn from_formatted_str(input: &str) -> Result<Self, FromStrError> {
        let remainder = input
            .strip_prefix(UREF_FORMATTED_STRING_PREFIX)
            .ok_or(FromStrError::InvalidPrefix)?;
        let parts = remainder.splitn(2, '-').collect::<Vec<_>>();
        if parts.len() != 2 {
            return Err(FromStrError::MissingSuffix);
        }
        let addr = URefAddr::try_from(checksummed_hex::decode(parts[0])?.as_ref())?;
        let access_rights_value = u8::from_str_radix(parts[1], 8)?;
        let access_rights = AccessRights::from_bits(access_rights_value)
            .ok_or(FromStrError::InvalidAccessRights)?;
        Ok(URef(addr, access_rights))
    }
    pub fn disable_access_rights(&mut self, access_rights: AccessRights) {
        self.1.remove(access_rights)
    }
}
#[cfg(feature = "json-schema")]
impl JsonSchema for URef {
    fn schema_name() -> String {
        String::from("URef")
    }
    fn json_schema(gen: &mut SchemaGenerator) -> Schema {
        let schema = gen.subschema_for::<String>();
        let mut schema_object = schema.into_object();
        schema_object.metadata().description = Some(String::from("Hex-encoded, formatted URef."));
        schema_object.into()
    }
}
impl Display for URef {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        let addr = self.addr();
        let access_rights = self.access_rights();
        write!(
            f,
            "URef({}, {})",
            base16::encode_lower(&addr),
            access_rights
        )
    }
}
impl Debug for URef {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "{}", self)
    }
}
impl bytesrepr::ToBytes for URef {
    fn to_bytes(&self) -> Result<Vec<u8>, Error> {
        let mut result = bytesrepr::unchecked_allocate_buffer(self);
        result.append(&mut self.0.to_bytes()?);
        result.append(&mut self.1.to_bytes()?);
        Ok(result)
    }
    fn serialized_length(&self) -> usize {
        UREF_SERIALIZED_LENGTH
    }
    fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), self::Error> {
        writer.extend_from_slice(&self.0);
        self.1.write_bytes(writer)?;
        Ok(())
    }
}
impl FromBytes for URef {
    fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> {
        let (id, rem) = FromBytes::from_bytes(bytes)?;
        let (access_rights, rem) = FromBytes::from_bytes(rem)?;
        Ok((URef(id, access_rights), rem))
    }
}
impl Serialize for URef {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        if serializer.is_human_readable() {
            self.to_formatted_string().serialize(serializer)
        } else {
            (self.0, self.1).serialize(serializer)
        }
    }
}
impl<'de> Deserialize<'de> for URef {
    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        if deserializer.is_human_readable() {
            let formatted_string = String::deserialize(deserializer)?;
            URef::from_formatted_str(&formatted_string).map_err(D::Error::custom)
        } else {
            let (address, access_rights) = <(URefAddr, AccessRights)>::deserialize(deserializer)?;
            Ok(URef(address, access_rights))
        }
    }
}
impl TryFrom<Key> for URef {
    type Error = ApiError;
    fn try_from(key: Key) -> Result<Self, Self::Error> {
        if let Key::URef(uref) = key {
            Ok(uref)
        } else {
            Err(ApiError::UnexpectedKeyVariant)
        }
    }
}
impl Distribution<URef> for Standard {
    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> URef {
        URef::new(rng.gen(), rng.gen())
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn uref_as_string() {
        let addr_array = [0u8; 32];
        let uref_a = URef::new(addr_array, AccessRights::READ);
        assert_eq!(
            uref_a.to_formatted_string(),
            "uref-0000000000000000000000000000000000000000000000000000000000000000-001"
        );
        let uref_b = URef::new(addr_array, AccessRights::WRITE);
        assert_eq!(
            uref_b.to_formatted_string(),
            "uref-0000000000000000000000000000000000000000000000000000000000000000-002"
        );
        let uref_c = uref_b.remove_access_rights();
        assert_eq!(
            uref_c.to_formatted_string(),
            "uref-0000000000000000000000000000000000000000000000000000000000000000-000"
        );
    }
    fn round_trip(uref: URef) {
        let string = uref.to_formatted_string();
        let parsed_uref = URef::from_formatted_str(&string).unwrap();
        assert_eq!(uref, parsed_uref);
    }
    #[test]
    fn uref_from_str() {
        round_trip(URef::new([0; 32], AccessRights::NONE));
        round_trip(URef::new([255; 32], AccessRights::READ_ADD_WRITE));
        let invalid_prefix =
            "ref-0000000000000000000000000000000000000000000000000000000000000000-000";
        assert!(URef::from_formatted_str(invalid_prefix).is_err());
        let invalid_prefix =
            "uref0000000000000000000000000000000000000000000000000000000000000000-000";
        assert!(URef::from_formatted_str(invalid_prefix).is_err());
        let short_addr = "uref-00000000000000000000000000000000000000000000000000000000000000-000";
        assert!(URef::from_formatted_str(short_addr).is_err());
        let long_addr =
            "uref-000000000000000000000000000000000000000000000000000000000000000000-000";
        assert!(URef::from_formatted_str(long_addr).is_err());
        let invalid_hex =
            "uref-000000000000000000000000000000000000000000000000000000000000000g-000";
        assert!(URef::from_formatted_str(invalid_hex).is_err());
        let invalid_suffix_separator =
            "uref-0000000000000000000000000000000000000000000000000000000000000000:000";
        assert!(URef::from_formatted_str(invalid_suffix_separator).is_err());
        let invalid_suffix =
            "uref-0000000000000000000000000000000000000000000000000000000000000000-abc";
        assert!(URef::from_formatted_str(invalid_suffix).is_err());
        let invalid_access_rights =
            "uref-0000000000000000000000000000000000000000000000000000000000000000-200";
        assert!(URef::from_formatted_str(invalid_access_rights).is_err());
    }
    #[test]
    fn serde_roundtrip() {
        let uref = URef::new([255; 32], AccessRights::READ_ADD_WRITE);
        let serialized = bincode::serialize(&uref).unwrap();
        let decoded = bincode::deserialize(&serialized).unwrap();
        assert_eq!(uref, decoded);
    }
    #[test]
    fn json_roundtrip() {
        let uref = URef::new([255; 32], AccessRights::READ_ADD_WRITE);
        let json_string = serde_json::to_string_pretty(&uref).unwrap();
        let decoded = serde_json::from_str(&json_string).unwrap();
        assert_eq!(uref, decoded);
    }
    #[test]
    fn should_disable_access_rights() {
        let mut uref = URef::new([255; 32], AccessRights::READ_ADD_WRITE);
        assert!(uref.is_writeable());
        uref.disable_access_rights(AccessRights::WRITE);
        assert_eq!(uref.access_rights(), AccessRights::READ_ADD);
        uref.disable_access_rights(AccessRights::WRITE);
        assert!(
            !uref.is_writeable(),
            "Disabling access bit twice should be a noop"
        );
        assert_eq!(uref.access_rights(), AccessRights::READ_ADD);
        uref.disable_access_rights(AccessRights::READ_ADD);
        assert_eq!(uref.access_rights(), AccessRights::NONE);
        uref.disable_access_rights(AccessRights::READ_ADD);
        assert_eq!(uref.access_rights(), AccessRights::NONE);
        uref.disable_access_rights(AccessRights::NONE);
        assert_eq!(uref.access_rights(), AccessRights::NONE);
    }
}