use crate::{
    bcs_scalar,
    crypto::{BcsHashable, CryptoError, CryptoHash, PublicKey},
    data_types::BlockHeight,
    doc_scalar,
};
use serde::{Deserialize, Serialize};
use std::str::FromStr;
#[cfg(any(test, feature = "test"))]
use test_strategy::Arbitrary;
#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Debug, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test"), derive(Default))]
pub struct Owner(pub CryptoHash);
#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Debug, Serialize, Deserialize)]
pub enum ChainDescription {
    Root(u32),
    Child(MessageId),
}
#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test"), derive(Arbitrary, Default))]
pub struct ChainId(pub CryptoHash);
#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Debug, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test"), derive(Default))]
pub struct MessageId {
    pub chain_id: ChainId,
    pub height: BlockHeight,
    pub index: u32,
}
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test"), derive(Default))]
#[serde(rename = "UserApplicationId")]
pub struct ApplicationId<A = ()> {
    pub bytecode_id: BytecodeId<A>,
    pub creation: MessageId,
}
#[derive(Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
#[cfg_attr(any(test, feature = "test"), derive(Default))]
pub struct BytecodeId<A = ()> {
    pub message_id: MessageId,
    #[serde(skip)]
    _phantom: std::marker::PhantomData<A>,
}
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct SessionId<A = ()> {
    pub application_id: ApplicationId<A>,
    pub index: u64,
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct ChannelName(#[serde(with = "serde_bytes")] Vec<u8>);
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
pub enum Destination {
    Recipient(ChainId),
    Subscribers(ChannelName),
}
impl From<ChainId> for Destination {
    fn from(chain_id: ChainId) -> Self {
        Destination::Recipient(chain_id)
    }
}
impl AsRef<[u8]> for ChannelName {
    fn as_ref(&self) -> &[u8] {
        &self.0
    }
}
impl From<Vec<u8>> for ChannelName {
    fn from(name: Vec<u8>) -> Self {
        ChannelName(name)
    }
}
impl ChannelName {
    pub fn into_bytes(self) -> Vec<u8> {
        self.0
    }
}
impl<A> Clone for BytecodeId<A> {
    fn clone(&self) -> Self {
        Self {
            message_id: self.message_id,
            _phantom: std::marker::PhantomData,
        }
    }
}
impl<A> Copy for BytecodeId<A> {}
impl BytecodeId {
    pub fn new(message_id: MessageId) -> Self {
        BytecodeId {
            message_id,
            _phantom: std::marker::PhantomData,
        }
    }
    pub fn with_abi<A>(self) -> BytecodeId<A> {
        BytecodeId {
            message_id: self.message_id,
            _phantom: std::marker::PhantomData,
        }
    }
}
impl<A> BytecodeId<A> {
    pub fn forget_abi(self) -> BytecodeId {
        BytecodeId {
            message_id: self.message_id,
            _phantom: std::marker::PhantomData,
        }
    }
}
impl<A> Clone for ApplicationId<A> {
    fn clone(&self) -> Self {
        Self {
            bytecode_id: self.bytecode_id,
            creation: self.creation,
        }
    }
}
impl<A> Copy for ApplicationId<A> {}
impl ApplicationId {
    pub fn with_abi<A>(self) -> ApplicationId<A> {
        ApplicationId {
            bytecode_id: self.bytecode_id.with_abi(),
            creation: self.creation,
        }
    }
}
impl<A> ApplicationId<A> {
    pub fn forget_abi(self) -> ApplicationId {
        ApplicationId {
            bytecode_id: self.bytecode_id.forget_abi(),
            creation: self.creation,
        }
    }
}
impl<A> Clone for SessionId<A> {
    fn clone(&self) -> Self {
        Self {
            application_id: self.application_id,
            index: self.index,
        }
    }
}
impl<A> Copy for SessionId<A> {}
impl SessionId {
    pub fn with_abi<A>(self) -> SessionId<A> {
        SessionId {
            application_id: self.application_id.with_abi(),
            index: self.index,
        }
    }
}
impl<A> SessionId<A> {
    pub fn forget_abi(self) -> SessionId {
        SessionId {
            application_id: self.application_id.forget_abi(),
            index: self.index,
        }
    }
}
impl std::fmt::Display for Owner {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
        self.0.fmt(f)
    }
}
impl From<PublicKey> for Owner {
    fn from(value: PublicKey) -> Self {
        Self(CryptoHash::new(&value))
    }
}
impl From<&PublicKey> for Owner {
    fn from(value: &PublicKey) -> Self {
        Self(CryptoHash::new(value))
    }
}
impl std::str::FromStr for Owner {
    type Err = CryptoError;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(Owner(CryptoHash::from_str(s)?))
    }
}
impl std::fmt::Display for ChainId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)
    }
}
impl FromStr for ChainId {
    type Err = CryptoError;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(ChainId(CryptoHash::from_str(s)?))
    }
}
impl TryFrom<&[u8]> for ChainId {
    type Error = CryptoError;
    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
        Ok(ChainId(CryptoHash::try_from(value)?))
    }
}
impl std::fmt::Debug for ChainId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
        write!(f, "{:?}", self.0)
    }
}
impl From<ChainDescription> for ChainId {
    fn from(description: ChainDescription) -> Self {
        Self(CryptoHash::new(&description))
    }
}
impl ChainId {
    pub fn root(index: u32) -> Self {
        Self(CryptoHash::new(&ChainDescription::Root(index)))
    }
    pub fn child(id: MessageId) -> Self {
        Self(CryptoHash::new(&ChainDescription::Child(id)))
    }
}
impl BcsHashable for ChainDescription {}
bcs_scalar!(ApplicationId, "A unique identifier for a user application");
bcs_scalar!(
    BytecodeId,
    "A unique identifier for an application bytecode"
);
doc_scalar!(ChainDescription, "How to create a chain");
doc_scalar!(
    ChainId,
    "The unique identifier (UID) of a chain. This is currently computed as the hash value of a \
    ChainDescription."
);
doc_scalar!(ChannelName, "The name of a subscription channel");
bcs_scalar!(MessageId, "The index of a message in a chain");
doc_scalar!(
    Owner,
    "The owner of a chain. This is currently the hash of the owner's public key used to verify \
    signatures."
);
#[cfg(test)]
mod tests {
    use super::ChainId;
    #[test]
    fn chain_ids() {
        assert_eq!(
            &ChainId::root(0).to_string(),
            "e476187f6ddfeb9d588c7b45d3df334d5501d6499b3f9ad5595cae86cce16a65"
        );
        assert_eq!(
            &ChainId::root(9).to_string(),
            "256e1dbc00482ddd619c293cc0df94d366afe7980022bb22d99e33036fd465dd"
        );
        assert_eq!(
            &ChainId::root(999).to_string(),
            "9c8a838e8f7b63194f6c7585455667a8379d2b5db19a3300e9961f0b1e9091ea"
        );
    }
}