#![doc = include_str!("../README.md")]

#[allow(clippy::all)]
#[allow(dead_code)]
#[allow(missing_docs)]
pub mod generated {
    include!(concat!(env!("OUT_DIR"), "/mod.rs"));
}

#[cfg(feature = "serde")]
pub mod json;

use bech32::{Bech32, Hrp};
use blake2::{
    Blake2b,
    digest::{Digest, consts::U32},
};
type Blake2b256 = Blake2b<U32>;
#[cfg(feature = "serde")]
pub use json::Transaction as JsonTransaction;
pub use prost::Message;

/// Human-readable part for `MultiversX` addresses.
const ERD_HRP: Hrp = Hrp::parse_unchecked("erd");

impl generated::proto::Transaction {
    /// Length in bytes of the transaction hash computed by the network (BLAKE2b-256).
    pub const HASH_SIZE: usize = 32;

    /// Computes the canonical transaction hash by re-encoding the message and hashing it with
    /// BLAKE2b-256, mirroring the logic from the Go implementation.
    #[must_use]
    pub fn compute_hash(&self) -> [u8; Self::HASH_SIZE] {
        let encoded = self.encode_to_vec();
        Self::hash_bytes(&encoded)
    }

    /// Computes the canonical transaction hash by re-encoding the message and hashing it with
    /// BLAKE2b-256.
    #[must_use]
    pub fn get_tx_hash(&self) -> [u8; Self::HASH_SIZE] {
        self.compute_hash()
    }

    /// Hashes an already serialized transaction buffer with BLAKE2b-256.
    #[must_use]
    pub fn hash_bytes(bytes: &[u8]) -> [u8; Self::HASH_SIZE] {
        let digest = Blake2b256::digest(bytes);
        digest.into()
    }

    fn bech32_address(bytes: &[u8]) -> Result<Option<String>, bech32::EncodeError> {
        if bytes.is_empty() {
            return Ok(None);
        }

        let encoded = bech32::encode::<Bech32>(ERD_HRP, bytes)?;
        Ok(Some(encoded))
    }

    /// Returns the sender address encoded as an `erd` Bech32 string if present.
    pub fn sender_address_bech32(&self) -> Result<Option<String>, bech32::EncodeError> {
        Self::bech32_address(self.snd_addr.as_ref())
    }

    /// Returns the sender address encoded as an `erd` Bech32 string if present.
    pub fn sender_bech32(&self) -> Result<Option<String>, bech32::EncodeError> {
        self.sender_address_bech32()
    }

    /// Returns the receiver address encoded as an `erd` Bech32 string if present.
    pub fn receiver_address_bech32(&self) -> Result<Option<String>, bech32::EncodeError> {
        Self::bech32_address(self.rcv_addr.as_ref())
    }

    /// Returns the receiver address encoded as an `erd` Bech32 string if present.
    pub fn receiver_bech32(&self) -> Result<Option<String>, bech32::EncodeError> {
        self.receiver_address_bech32()
    }

    /// Returns the guardian address encoded as an `erd` Bech32 string if present.
    pub fn guardian_address_bech32(&self) -> Result<Option<String>, bech32::EncodeError> {
        Self::bech32_address(self.guardian_addr.as_ref())
    }

    /// Returns the guardian address encoded as an `erd` Bech32 string if present.
    pub fn guardian_bech32(&self) -> Result<Option<String>, bech32::EncodeError> {
        self.guardian_address_bech32()
    }

    /// Returns the relayer address encoded as an `erd` Bech32 string if present.
    pub fn relayer_address_bech32(&self) -> Result<Option<String>, bech32::EncodeError> {
        Self::bech32_address(self.relayer_addr.as_ref())
    }

    /// Returns the relayer address encoded as an `erd` Bech32 string if present.
    pub fn relayer_bech32(&self) -> Result<Option<String>, bech32::EncodeError> {
        self.relayer_address_bech32()
    }
}

#[cfg(test)]
mod tests {
    use super::ERD_HRP;
    use super::generated::proto::Transaction;
    use bech32::Bech32;
    use hex::FromHex;
    use prost::Message;
    use prost::bytes::Bytes;

    #[test]
    fn hash_bytes_matches_known_digests() {
        let expected_empty = <[u8; Transaction::HASH_SIZE]>::from_hex(
            "0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8",
        )
        .unwrap();
        assert_eq!(Transaction::hash_bytes(b""), expected_empty);

        let expected_hello = <[u8; Transaction::HASH_SIZE]>::from_hex(
            "256c83b297114d201b30179f3f0ef0cace9783622da5974326b436178aeef610",
        )
        .unwrap();
        assert_eq!(Transaction::hash_bytes(b"hello world"), expected_hello);
    }

    #[test]
    fn compute_hash_reencodes_transaction_like_go() {
        let tx = Transaction {
            nonce: 1,
            value: Bytes::new(),
            rcv_addr: Bytes::new(),
            rcv_user_name: Bytes::new(),
            snd_addr: Bytes::new(),
            snd_user_name: Bytes::new(),
            gas_price: 0,
            gas_limit: 0,
            data: Bytes::new(),
            chain_id: Bytes::new(),
            version: 0,
            signature: Bytes::new(),
            options: 0,
            guardian_addr: Bytes::new(),
            guardian_signature: Bytes::new(),
            relayer_addr: Bytes::new(),
            relayer_signature: Bytes::new(),
        };

        let expected = <[u8; Transaction::HASH_SIZE]>::from_hex(
            "890b1d2195b2db958c0b3c02d09997776a5f7c0fc2daf30f3bf8469b841c30e9",
        )
        .unwrap();

        // Double-check that the encoded bytes match the manual expectation for field #1 (nonce).
        let encoded = tx.encode_to_vec();
        assert_eq!(encoded, [0x08, 0x01]);

        assert_eq!(tx.compute_hash(), expected);
    }

    #[test]
    fn bech32_address_helpers_encode_addresses() {
        let sender_vec: Vec<u8> = (0..32).collect();
        let receiver_vec: Vec<u8> = (32..64).collect();
        let relayer_vec: Vec<u8> = (64..96).collect();

        let tx = Transaction {
            snd_addr: Bytes::from(sender_vec.clone()),
            rcv_addr: Bytes::from(receiver_vec.clone()),
            relayer_addr: Bytes::from(relayer_vec.clone()),
            ..Default::default()
        };

        let expected_sender = bech32::encode::<Bech32>(ERD_HRP, &sender_vec).unwrap();
        let expected_receiver = bech32::encode::<Bech32>(ERD_HRP, &receiver_vec).unwrap();
        let expected_relayer = bech32::encode::<Bech32>(ERD_HRP, &relayer_vec).unwrap();

        assert_eq!(tx.sender_address_bech32().unwrap(), Some(expected_sender));
        assert_eq!(
            tx.sender_bech32().unwrap(),
            tx.sender_address_bech32().unwrap()
        );
        assert_eq!(
            tx.receiver_address_bech32().unwrap(),
            Some(expected_receiver)
        );
        assert_eq!(
            tx.receiver_bech32().unwrap(),
            tx.receiver_address_bech32().unwrap()
        );
        assert_eq!(tx.guardian_address_bech32().unwrap(), None);
        assert_eq!(
            tx.guardian_bech32().unwrap(),
            tx.guardian_address_bech32().unwrap()
        );
        assert_eq!(tx.relayer_address_bech32().unwrap(), Some(expected_relayer));
        assert_eq!(
            tx.relayer_bech32().unwrap(),
            tx.relayer_address_bech32().unwrap()
        );
    }

    #[test]
    fn test_hash_bytes_large_input() {
        // Test with 10KB+ input to verify hashing handles large data
        let large_input: Vec<u8> = (0..=255).cycle().take(10 * 1024).collect();
        let hash = Transaction::hash_bytes(&large_input);

        // Hash should be 32 bytes
        assert_eq!(hash.len(), Transaction::HASH_SIZE);

        // Hash should be deterministic
        let hash2 = Transaction::hash_bytes(&large_input);
        assert_eq!(hash, hash2);

        // Hash should be non-zero
        assert_ne!(hash, [0u8; 32]);
    }

    #[test]
    fn test_hash_bytes_all_zeros() {
        let zeros = vec![0u8; 1024];
        let hash = Transaction::hash_bytes(&zeros);

        // Hash should be 32 bytes
        assert_eq!(hash.len(), Transaction::HASH_SIZE);

        // Hash of all zeros should not be all zeros
        assert_ne!(hash, [0u8; 32]);

        // Verify determinism
        assert_eq!(hash, Transaction::hash_bytes(&zeros));
    }

    #[test]
    fn test_hash_bytes_all_ones() {
        let ones = vec![0xFFu8; 1024];
        let hash = Transaction::hash_bytes(&ones);

        // Hash should be 32 bytes
        assert_eq!(hash.len(), Transaction::HASH_SIZE);

        // Hash of all 0xFF should not be all 0xFF
        assert_ne!(hash, [0xFFu8; 32]);

        // Verify determinism
        assert_eq!(hash, Transaction::hash_bytes(&ones));

        // Should differ from all zeros hash
        let zeros = vec![0u8; 1024];
        assert_ne!(hash, Transaction::hash_bytes(&zeros));
    }

    #[test]
    fn test_compute_hash_empty_tx() {
        let tx = Transaction::default();
        let hash = tx.compute_hash();

        // Hash should be 32 bytes
        assert_eq!(hash.len(), Transaction::HASH_SIZE);

        // Empty tx encodes to empty bytes, which has a known hash
        let expected_empty = <[u8; Transaction::HASH_SIZE]>::from_hex(
            "0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8",
        )
        .unwrap();
        assert_eq!(hash, expected_empty);
    }

    #[test]
    fn test_compute_hash_full_tx() {
        // Transaction with all fields populated
        let tx = Transaction {
            nonce: 42,
            value: Bytes::from(vec![0x01, 0x00, 0x00, 0x00]), // 1 EGLD in big-endian
            rcv_addr: Bytes::from(vec![1u8; 32]),
            rcv_user_name: Bytes::from("receiver_name"),
            snd_addr: Bytes::from(vec![2u8; 32]),
            snd_user_name: Bytes::from("sender_name"),
            gas_price: 1_000_000_000,
            gas_limit: 50_000,
            data: Bytes::from("transfer@01"),
            chain_id: Bytes::from("1"),
            version: 2,
            signature: Bytes::from(vec![3u8; 64]),
            options: 1,
            guardian_addr: Bytes::from(vec![4u8; 32]),
            guardian_signature: Bytes::from(vec![5u8; 64]),
            relayer_addr: Bytes::from(vec![6u8; 32]),
            relayer_signature: Bytes::from(vec![7u8; 64]),
        };

        let hash = tx.compute_hash();

        // Hash should be 32 bytes
        assert_eq!(hash.len(), Transaction::HASH_SIZE);

        // Hash should be non-zero
        assert_ne!(hash, [0u8; 32]);

        // Hash should differ from empty tx
        assert_ne!(hash, Transaction::default().compute_hash());
    }

    #[test]
    fn test_compute_hash_deterministic() {
        let tx = Transaction {
            nonce: 100,
            value: Bytes::from(vec![0x0A]),
            rcv_addr: Bytes::from(vec![0xAB; 32]),
            snd_addr: Bytes::from(vec![0xCD; 32]),
            gas_price: 500_000_000,
            gas_limit: 100_000,
            data: Bytes::from("test_data"),
            chain_id: Bytes::from("T"),
            version: 1,
            ..Default::default()
        };

        // Compute hash multiple times
        let hash1 = tx.compute_hash();
        let hash2 = tx.compute_hash();
        let hash3 = tx.compute_hash();

        // All hashes should be identical
        assert_eq!(hash1, hash2);
        assert_eq!(hash2, hash3);

        // Clone and verify cloned tx produces same hash
        let tx_cloned = tx.clone();
        assert_eq!(tx.compute_hash(), tx_cloned.compute_hash());
        assert_eq!(tx.get_tx_hash(), tx.compute_hash());
    }

    #[test]
    fn test_sender_address_empty() {
        let tx = Transaction {
            snd_addr: Bytes::new(),
            ..Default::default()
        };

        let result = tx.sender_address_bech32().unwrap();
        assert_eq!(result, None);
    }

    #[test]
    fn test_receiver_address_empty() {
        let tx = Transaction {
            rcv_addr: Bytes::new(),
            ..Default::default()
        };

        let result = tx.receiver_address_bech32().unwrap();
        assert_eq!(result, None);
    }

    #[test]
    fn test_guardian_address_populated() {
        let guardian_bytes: Vec<u8> = (100..132).collect(); // 32 bytes
        let tx = Transaction {
            guardian_addr: Bytes::from(guardian_bytes.clone()),
            ..Default::default()
        };

        let result = tx.guardian_address_bech32().unwrap();
        assert!(result.is_some());

        let expected = bech32::encode::<Bech32>(ERD_HRP, &guardian_bytes).unwrap();
        assert_eq!(result.unwrap(), expected);
        assert_eq!(
            tx.guardian_bech32().unwrap(),
            tx.guardian_address_bech32().unwrap()
        );
    }

    #[test]
    fn test_relayer_address_populated() {
        let relayer_bytes: Vec<u8> = (150..182).collect(); // 32 bytes
        let tx = Transaction {
            relayer_addr: Bytes::from(relayer_bytes.clone()),
            ..Default::default()
        };

        let result = tx.relayer_address_bech32().unwrap();
        assert!(result.is_some());

        let expected = bech32::encode::<Bech32>(ERD_HRP, &relayer_bytes).unwrap();
        assert_eq!(result.unwrap(), expected);
        assert_eq!(
            tx.relayer_bech32().unwrap(),
            tx.relayer_address_bech32().unwrap()
        );
    }

    #[test]
    fn test_all_addresses_format() {
        let sender_bytes: Vec<u8> = (0..32).collect();
        let receiver_bytes: Vec<u8> = (32..64).collect();
        let guardian_bytes: Vec<u8> = (64..96).collect();
        let relayer_bytes: Vec<u8> = (96..128).collect();

        let tx = Transaction {
            snd_addr: Bytes::from(sender_bytes),
            rcv_addr: Bytes::from(receiver_bytes),
            guardian_addr: Bytes::from(guardian_bytes),
            relayer_addr: Bytes::from(relayer_bytes),
            ..Default::default()
        };

        // All addresses should start with "erd1"
        let sender = tx.sender_bech32().unwrap().unwrap();
        let receiver = tx.receiver_bech32().unwrap().unwrap();
        let guardian = tx.guardian_bech32().unwrap().unwrap();
        let relayer = tx.relayer_bech32().unwrap().unwrap();

        assert!(
            sender.starts_with("erd1"),
            "Sender should start with erd1, got: {sender}"
        );
        assert!(
            receiver.starts_with("erd1"),
            "Receiver should start with erd1, got: {receiver}"
        );
        assert!(
            guardian.starts_with("erd1"),
            "Guardian should start with erd1, got: {guardian}"
        );
        assert!(
            relayer.starts_with("erd1"),
            "Relayer should start with erd1, got: {relayer}"
        );

        // All addresses should be valid bech32 strings of expected length
        // MultiversX addresses are 62 characters (erd1 + 58 chars)
        assert_eq!(sender.len(), 62);
        assert_eq!(receiver.len(), 62);
        assert_eq!(guardian.len(), 62);
        assert_eq!(relayer.len(), 62);
    }
}
