use alloc::borrow::Cow;
use core::fmt;
use core::ops::Deref;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use sha2::{
    digest::{Digest, Update},
    Sha256,
};
use thiserror::Error;
use crate::{binary::Binary, forward_ref_partial_eq, HexBinary};
#[derive(
    Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, JsonSchema,
)]
pub struct Addr(String);
forward_ref_partial_eq!(Addr, Addr);
impl Addr {
    pub fn unchecked(input: impl Into<String>) -> Addr {
        Addr(input.into())
    }
    #[inline]
    pub fn as_str(&self) -> &str {
        self.0.as_str()
    }
    #[inline]
    pub fn as_bytes(&self) -> &[u8] {
        self.0.as_bytes()
    }
    #[inline]
    pub fn into_string(self) -> String {
        self.0
    }
}
impl fmt::Display for Addr {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", &self.0)
    }
}
impl AsRef<str> for Addr {
    #[inline]
    fn as_ref(&self) -> &str {
        self.as_str()
    }
}
impl PartialEq<&str> for Addr {
    fn eq(&self, rhs: &&str) -> bool {
        self.0 == *rhs
    }
}
impl PartialEq<Addr> for &str {
    fn eq(&self, rhs: &Addr) -> bool {
        *self == rhs.0
    }
}
impl PartialEq<String> for Addr {
    fn eq(&self, rhs: &String) -> bool {
        &self.0 == rhs
    }
}
impl PartialEq<Addr> for String {
    fn eq(&self, rhs: &Addr) -> bool {
        self == &rhs.0
    }
}
impl From<Addr> for String {
    fn from(addr: Addr) -> Self {
        addr.0
    }
}
impl From<&Addr> for String {
    fn from(addr: &Addr) -> Self {
        addr.0.clone()
    }
}
impl From<Addr> for Cow<'_, Addr> {
    fn from(addr: Addr) -> Self {
        Cow::Owned(addr)
    }
}
impl<'a> From<&'a Addr> for Cow<'a, Addr> {
    fn from(addr: &'a Addr) -> Self {
        Cow::Borrowed(addr)
    }
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash, JsonSchema)]
pub struct CanonicalAddr(pub Binary);
impl PartialEq<Binary> for CanonicalAddr {
    fn eq(&self, rhs: &Binary) -> bool {
        &self.0 == rhs
    }
}
impl PartialEq<CanonicalAddr> for Binary {
    fn eq(&self, rhs: &CanonicalAddr) -> bool {
        self == &rhs.0
    }
}
impl PartialEq<HexBinary> for CanonicalAddr {
    fn eq(&self, rhs: &HexBinary) -> bool {
        self.as_slice() == rhs.as_slice()
    }
}
impl PartialEq<CanonicalAddr> for HexBinary {
    fn eq(&self, rhs: &CanonicalAddr) -> bool {
        self.as_slice() == rhs.0.as_slice()
    }
}
impl From<&[u8]> for CanonicalAddr {
    fn from(source: &[u8]) -> Self {
        Self(source.into())
    }
}
impl<const LENGTH: usize> From<&[u8; LENGTH]> for CanonicalAddr {
    fn from(source: &[u8; LENGTH]) -> Self {
        Self(source.into())
    }
}
impl<const LENGTH: usize> From<[u8; LENGTH]> for CanonicalAddr {
    fn from(source: [u8; LENGTH]) -> Self {
        Self(source.into())
    }
}
impl From<Vec<u8>> for CanonicalAddr {
    fn from(source: Vec<u8>) -> Self {
        Self(source.into())
    }
}
impl From<CanonicalAddr> for Vec<u8> {
    fn from(source: CanonicalAddr) -> Vec<u8> {
        source.0.into()
    }
}
impl From<Binary> for CanonicalAddr {
    fn from(source: Binary) -> Self {
        Self(source)
    }
}
impl From<CanonicalAddr> for Binary {
    fn from(source: CanonicalAddr) -> Binary {
        source.0
    }
}
impl From<HexBinary> for CanonicalAddr {
    fn from(source: HexBinary) -> Self {
        Self(source.into())
    }
}
impl From<CanonicalAddr> for HexBinary {
    fn from(source: CanonicalAddr) -> HexBinary {
        source.0.into()
    }
}
impl Deref for CanonicalAddr {
    type Target = [u8];
    fn deref(&self) -> &Self::Target {
        self.as_slice()
    }
}
impl CanonicalAddr {
    pub fn as_slice(&self) -> &[u8] {
        self.0.as_slice()
    }
}
impl fmt::Display for CanonicalAddr {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        for byte in self.0.as_slice() {
            write!(f, "{byte:02X}")?;
        }
        Ok(())
    }
}
#[derive(Error, Debug, PartialEq, Eq)]
pub enum Instantiate2AddressError {
    InvalidChecksumLength,
    InvalidSaltLength,
}
impl fmt::Display for Instantiate2AddressError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Instantiate2AddressError::InvalidChecksumLength => write!(f, "invalid checksum length"),
            Instantiate2AddressError::InvalidSaltLength => write!(f, "invalid salt length"),
        }
    }
}
pub fn instantiate2_address(
    checksum: &[u8],
    creator: &CanonicalAddr,
    salt: &[u8],
) -> Result<CanonicalAddr, Instantiate2AddressError> {
    let msg = b"";
    instantiate2_address_impl(checksum, creator, salt, msg)
}
#[doc(hidden)]
fn instantiate2_address_impl(
    checksum: &[u8],
    creator: &CanonicalAddr,
    salt: &[u8],
    msg: &[u8],
) -> Result<CanonicalAddr, Instantiate2AddressError> {
    if checksum.len() != 32 {
        return Err(Instantiate2AddressError::InvalidChecksumLength);
    }
    if salt.is_empty() || salt.len() > 64 {
        return Err(Instantiate2AddressError::InvalidSaltLength);
    };
    let mut key = Vec::<u8>::new();
    key.extend_from_slice(b"wasm\0");
    key.extend_from_slice(&(checksum.len() as u64).to_be_bytes());
    key.extend_from_slice(checksum);
    key.extend_from_slice(&(creator.len() as u64).to_be_bytes());
    key.extend_from_slice(creator);
    key.extend_from_slice(&(salt.len() as u64).to_be_bytes());
    key.extend_from_slice(salt);
    key.extend_from_slice(&(msg.len() as u64).to_be_bytes());
    key.extend_from_slice(msg);
    let address_data = hash("module", &key);
    Ok(address_data.into())
}
fn hash(ty: &str, key: &[u8]) -> Vec<u8> {
    let inner = Sha256::digest(ty.as_bytes());
    Sha256::new().chain(inner).chain(key).finalize().to_vec()
}
#[cfg(test)]
mod tests {
    use super::*;
    use crate::{assert_hash_works, HexBinary};
    use hex_literal::hex;
    #[test]
    fn addr_unchecked_works() {
        let a = Addr::unchecked("123");
        let aa = Addr::unchecked(String::from("123"));
        let b = Addr::unchecked("be");
        assert_eq!(a, aa);
        assert_ne!(a, b);
    }
    #[test]
    fn addr_as_str_works() {
        let addr = Addr::unchecked("literal-string");
        assert_eq!(addr.as_str(), "literal-string");
    }
    #[test]
    fn addr_as_bytes_works() {
        let addr = Addr::unchecked("literal-string");
        assert_eq!(
            addr.as_bytes(),
            [108, 105, 116, 101, 114, 97, 108, 45, 115, 116, 114, 105, 110, 103]
        );
    }
    #[test]
    fn addr_implements_display() {
        let addr = Addr::unchecked("cos934gh9034hg04g0h134");
        let embedded = format!("Address: {addr}");
        assert_eq!(embedded, "Address: cos934gh9034hg04g0h134");
        assert_eq!(addr.to_string(), "cos934gh9034hg04g0h134");
    }
    #[test]
    fn addr_implements_as_ref_for_str() {
        let addr = Addr::unchecked("literal-string");
        assert_eq!(addr.as_ref(), "literal-string");
    }
    #[test]
    fn addr_implements_partial_eq_with_str_and_string() {
        let addr = Addr::unchecked("cos934gh9034hg04g0h134");
        assert_eq!(addr, "cos934gh9034hg04g0h134");
        assert_eq!("cos934gh9034hg04g0h134", addr);
        assert_eq!(addr, String::from("cos934gh9034hg04g0h134"));
        assert_eq!(String::from("cos934gh9034hg04g0h134"), addr);
    }
    #[test]
    fn addr_implements_partial_eq_addr_ref() {
        let addr = Addr::unchecked("cos934gh9034hg04g0h134");
        let addr_ref = &addr;
        let addr_ref2 = &addr;
        assert_eq!(addr, addr_ref);
        assert_eq!(addr_ref, addr);
        assert_eq!(addr_ref, addr_ref2);
    }
    #[test]
    fn addr_implements_into_string() {
        let addr = Addr::unchecked("cos934gh9034hg04g0h134");
        let string: String = addr.into();
        assert_eq!(string, "cos934gh9034hg04g0h134");
        let addr = Addr::unchecked("cos934gh9034hg04g0h134");
        let addr_ref = &addr;
        let string: String = addr_ref.into();
        assert_eq!(string, "cos934gh9034hg04g0h134");
    }
    #[test]
    fn canonical_addr_from_slice() {
        let bytes: &[u8] = &[0u8, 187, 61, 11, 250, 0];
        let canonical_addr_slice = CanonicalAddr::from(bytes);
        assert_eq!(canonical_addr_slice.as_slice(), &[0u8, 187, 61, 11, 250, 0]);
        let bytes: Vec<u8> = vec![0u8, 187, 61, 11, 250, 0];
        let canonical_addr_vec = CanonicalAddr::from(bytes);
        assert_eq!(canonical_addr_vec.as_slice(), &[0u8, 187, 61, 11, 250, 0]);
    }
    #[test]
    fn canonical_addr_implements_partial_eq_with_binary() {
        let addr = CanonicalAddr::from([1, 2, 3]);
        let bin1 = Binary::from([1, 2, 3]);
        let bin2 = Binary::from([42, 43]);
        assert_eq!(addr, bin1);
        assert_eq!(bin1, addr);
        assert_ne!(addr, bin2);
        assert_ne!(bin2, addr);
    }
    #[test]
    fn canonical_addr_implements_partial_eq_with_hex_binary() {
        let addr = CanonicalAddr::from([1, 2, 3]);
        let bin1 = HexBinary::from([1, 2, 3]);
        let bin2 = HexBinary::from([42, 43]);
        assert_eq!(addr, bin1);
        assert_eq!(bin1, addr);
        assert_ne!(addr, bin2);
        assert_ne!(bin2, addr);
    }
    #[test]
    fn canonical_addr_implements_from_array() {
        let array = [1, 2, 3];
        let addr = CanonicalAddr::from(array);
        assert_eq!(addr.as_slice(), [1, 2, 3]);
        let array_ref = b"foo";
        let addr = CanonicalAddr::from(array_ref);
        assert_eq!(addr.as_slice(), [0x66, 0x6f, 0x6f]);
    }
    #[test]
    fn canonical_addr_implements_from_and_to_vector() {
        let original = vec![0u8, 187, 61, 11, 250, 0];
        let original_ptr = original.as_ptr();
        let addr: CanonicalAddr = original.into();
        assert_eq!(addr.as_slice(), [0u8, 187, 61, 11, 250, 0]);
        assert_eq!((addr.0).0.as_ptr(), original_ptr, "must not be copied");
        let original = vec![0u8, 187, 61, 11, 250, 0];
        let original_ptr = original.as_ptr();
        let addr = CanonicalAddr::from(original);
        assert_eq!(addr.as_slice(), [0u8, 187, 61, 11, 250, 0]);
        assert_eq!((addr.0).0.as_ptr(), original_ptr, "must not be copied");
        let original = CanonicalAddr::from(vec![0u8, 187, 61, 11, 250, 0]);
        let original_ptr = (original.0).0.as_ptr();
        let vec: Vec<u8> = original.into();
        assert_eq!(vec.as_slice(), [0u8, 187, 61, 11, 250, 0]);
        assert_eq!(vec.as_ptr(), original_ptr, "must not be copied");
        let original = CanonicalAddr::from(vec![7u8, 35, 49, 101, 0, 255]);
        let original_ptr = (original.0).0.as_ptr();
        let vec = Vec::<u8>::from(original);
        assert_eq!(vec.as_slice(), [7u8, 35, 49, 101, 0, 255]);
        assert_eq!(vec.as_ptr(), original_ptr, "must not be copied");
    }
    #[test]
    fn canonical_addr_implements_from_and_to_binary() {
        let original = Binary::from([0u8, 187, 61, 11, 250, 0]);
        let original_ptr = original.as_ptr();
        let addr = CanonicalAddr::from(original);
        assert_eq!(addr.as_slice(), [0u8, 187, 61, 11, 250, 0]);
        assert_eq!((addr.0).0.as_ptr(), original_ptr, "must not be copied");
        let original = CanonicalAddr::from(vec![7u8, 35, 49, 101, 0, 255]);
        let original_ptr = (original.0).0.as_ptr();
        let bin = Binary::from(original);
        assert_eq!(bin.as_slice(), [7u8, 35, 49, 101, 0, 255]);
        assert_eq!(bin.as_ptr(), original_ptr, "must not be copied");
    }
    #[test]
    fn canonical_addr_implements_from_and_to_hex_binary() {
        let original = HexBinary::from([0u8, 187, 61, 11, 250, 0]);
        let original_ptr = original.as_ptr();
        let addr = CanonicalAddr::from(original);
        assert_eq!(addr.as_slice(), [0u8, 187, 61, 11, 250, 0]);
        assert_eq!((addr.0).0.as_ptr(), original_ptr, "must not be copied");
        let original = CanonicalAddr::from(vec![7u8, 35, 49, 101, 0, 255]);
        let original_ptr = (original.0).0.as_ptr();
        let bin = HexBinary::from(original);
        assert_eq!(bin.as_slice(), [7u8, 35, 49, 101, 0, 255]);
        assert_eq!(bin.as_ptr(), original_ptr, "must not be copied");
    }
    #[test]
    fn canonical_addr_len() {
        let bytes: &[u8] = &[0u8, 187, 61, 11, 250, 0];
        let canonical_addr = CanonicalAddr::from(bytes);
        assert_eq!(canonical_addr.len(), bytes.len());
    }
    #[test]
    fn canonical_addr_is_empty() {
        let bytes: &[u8] = &[0u8, 187, 61, 11, 250, 0];
        let canonical_addr = CanonicalAddr::from(bytes);
        assert!(!canonical_addr.is_empty());
        let empty_canonical_addr = CanonicalAddr::from(vec![]);
        assert!(empty_canonical_addr.is_empty());
    }
    #[test]
    fn canonical_addr_implements_display() {
        let bytes: &[u8] = &[
            0x12, 0x03, 0xab, 0x00, 0xff,
        ];
        let address = CanonicalAddr::from(bytes);
        let embedded = format!("Address: {address}");
        assert_eq!(embedded, "Address: 1203AB00FF");
        assert_eq!(address.to_string(), "1203AB00FF");
    }
    #[test]
    fn canonical_addr_implements_deref() {
        let bytes: &[u8] = &[0u8, 187, 61, 11, 250, 0];
        let canonical_addr = CanonicalAddr::from(bytes);
        assert_eq!(*canonical_addr, [0u8, 187, 61, 11, 250, 0]);
        let bytes: &[u8] = &[0u8, 187, 61, 11, 250, 0];
        let canonical_addr = CanonicalAddr::from(bytes);
        assert_eq!(canonical_addr.len(), 6);
        let canonical_addr_slice: &[u8] = &canonical_addr;
        assert_eq!(canonical_addr_slice, &[0u8, 187, 61, 11, 250, 0]);
    }
    #[test]
    fn canonical_addr_implements_hash_eq() {
        let alice = CanonicalAddr::from([0, 187, 61, 11, 250, 0]);
        let bob = CanonicalAddr::from([16, 21, 33, 0, 255, 9]);
        assert_hash_works!(alice, bob);
    }
    fn flexible<'a>(a: impl Into<Cow<'a, Addr>>) -> String {
        a.into().into_owned().to_string()
    }
    #[test]
    fn addr_into_cow() {
        let value = "wasmeucn0ur0ncny2308ry";
        let addr = Addr::unchecked(value);
        assert_eq!(value, &flexible(&addr));
        assert_eq!(value, &flexible(addr));
    }
    #[test]
    fn instantiate2_address_impl_works() {
        let checksum1 =
            HexBinary::from_hex("13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5")
                .unwrap();
        let creator1 = CanonicalAddr::from(hex!("9999999999aaaaaaaaaabbbbbbbbbbcccccccccc"));
        let salt1 = hex!("61");
        let salt2 = hex!("aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae");
        let msg1: &[u8] = b"";
        let msg2: &[u8] = b"{}";
        let msg3: &[u8] = b"{\"some\":123,\"structure\":{\"nested\":[\"ok\",true]}}";
        let expected = CanonicalAddr::from(hex!(
            "5e865d3e45ad3e961f77fd77d46543417ced44d924dc3e079b5415ff6775f847"
        ));
        assert_eq!(
            instantiate2_address_impl(&checksum1, &creator1, &salt1, msg1).unwrap(),
            expected
        );
        let expected = CanonicalAddr::from(hex!(
            "0995499608947a5281e2c7ebd71bdb26a1ad981946dad57f6c4d3ee35de77835"
        ));
        assert_eq!(
            instantiate2_address_impl(&checksum1, &creator1, &salt1, msg2).unwrap(),
            expected
        );
        let expected = CanonicalAddr::from(hex!(
            "83326e554723b15bac664ceabc8a5887e27003abe9fbd992af8c7bcea4745167"
        ));
        assert_eq!(
            instantiate2_address_impl(&checksum1, &creator1, &salt1, msg3).unwrap(),
            expected
        );
        let expected = CanonicalAddr::from(hex!(
            "9384c6248c0bb171e306fd7da0993ec1e20eba006452a3a9e078883eb3594564"
        ));
        assert_eq!(
            instantiate2_address_impl(&checksum1, &creator1, &salt2, b"").unwrap(),
            expected
        );
        let empty = Vec::<u8>::new();
        assert!(matches!(
            instantiate2_address_impl(&checksum1, &creator1, &empty, b"").unwrap_err(),
            Instantiate2AddressError::InvalidSaltLength
        ));
        let too_long = vec![0x11; 65];
        assert!(matches!(
            instantiate2_address_impl(&checksum1, &creator1, &too_long, b"").unwrap_err(),
            Instantiate2AddressError::InvalidSaltLength
        ));
        let broken_cs = hex!("13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2");
        assert!(matches!(
            instantiate2_address_impl(&broken_cs, &creator1, &salt1, b"").unwrap_err(),
            Instantiate2AddressError::InvalidChecksumLength
        ));
        let broken_cs = hex!("");
        assert!(matches!(
            instantiate2_address_impl(&broken_cs, &creator1, &salt1, b"").unwrap_err(),
            Instantiate2AddressError::InvalidChecksumLength
        ));
        let broken_cs = hex!("13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2aaaa");
        assert!(matches!(
            instantiate2_address_impl(&broken_cs, &creator1, &salt1, b"").unwrap_err(),
            Instantiate2AddressError::InvalidChecksumLength
        ));
    }
    #[test]
    fn instantiate2_address_impl_works_for_cosmjs_test_vectors() {
        const COSMOS_ED25519_TESTS_JSON: &str = "./testdata/instantiate2_addresses.json";
        #[derive(Deserialize, Debug)]
        #[serde(rename_all = "camelCase")]
        #[allow(dead_code)]
        struct In {
            checksum: HexBinary,
            creator: String,
            creator_data: HexBinary,
            salt: HexBinary,
            msg: Option<String>,
        }
        #[derive(Deserialize, Debug)]
        #[serde(rename_all = "camelCase")]
        #[allow(dead_code)]
        struct Intermediate {
            key: HexBinary,
            address_data: HexBinary,
        }
        #[derive(Deserialize, Debug)]
        #[serde(rename_all = "camelCase")]
        #[allow(dead_code)]
        struct Out {
            address: String,
        }
        #[derive(Deserialize, Debug)]
        #[allow(dead_code)]
        struct Row {
            #[serde(rename = "in")]
            input: In,
            intermediate: Intermediate,
            out: Out,
        }
        fn read_tests() -> Vec<Row> {
            use std::fs::File;
            use std::io::BufReader;
            let file = File::open(COSMOS_ED25519_TESTS_JSON).unwrap();
            let reader = BufReader::new(file);
            serde_json::from_reader(reader).unwrap()
        }
        for Row {
            input,
            intermediate,
            out: _,
        } in read_tests()
        {
            let msg = input.msg.map(|msg| msg.into_bytes()).unwrap_or_default();
            let addr = instantiate2_address_impl(
                &input.checksum,
                &input.creator_data.into(),
                &input.salt,
                &msg,
            )
            .unwrap();
            assert_eq!(addr, intermediate.address_data);
        }
    }
    #[test]
    fn hash_works() {
        let expected = [
            195, 235, 23, 251, 9, 99, 177, 195, 81, 122, 182, 124, 36, 113, 245, 156, 76, 188, 221,
            83, 181, 192, 227, 82, 100, 177, 161, 133, 240, 160, 5, 25,
        ];
        assert_eq!(hash("1", &[1]), expected);
    }
}