use crate::{Address, B256, U256};
use fluentbase_crypto::crypto_keccak256;

#[inline(always)]
pub fn calc_create_address(deployer: &Address, nonce: u64) -> Address {
    use alloy_rlp::{Encodable, EMPTY_LIST_CODE, EMPTY_STRING_CODE};
    const MAX_LEN: usize = 1 + (1 + 20) + 9;
    let len = 22 + nonce.length();
    debug_assert!(len <= MAX_LEN);
    let mut out = [0u8; MAX_LEN];
    out[0] = EMPTY_LIST_CODE + len as u8 - 1;
    out[1] = EMPTY_STRING_CODE + 20;
    out[2..22].copy_from_slice(deployer.as_slice());
    Encodable::encode(&nonce, &mut &mut out[22..]);
    let out = &out[..len];
    Address::from_word(crypto_keccak256(out))
}

#[inline(always)]
pub fn calc_create2_address(deployer: &Address, salt: &U256, init_code_hash: &B256) -> Address {
    let mut bytes = [0; 85];
    bytes[0] = 0xff;
    bytes[1..21].copy_from_slice(deployer.as_slice());
    bytes[21..53].copy_from_slice(&salt.to_be_bytes::<32>());
    bytes[53..85].copy_from_slice(init_code_hash.as_slice());
    let hash = crypto_keccak256(bytes);
    Address::from_word(hash)
}

#[inline(always)]
pub fn calc_create_metadata_address(owner: &Address, salt: &U256) -> Address {
    let mut bytes = [0; 53];
    bytes[0] = 0x44;
    bytes[1..21].copy_from_slice(owner.as_slice());
    bytes[21..53].copy_from_slice(&salt.to_be_bytes::<32>());
    let hash = crypto_keccak256(bytes);
    Address::from_word(hash)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{address, b256};
    use fluentbase_types::{PRECOMPILE_ROLLUP_BRIDGE, PRECOMPILE_ROLLUP_BRIDGE_DEPLOYER};

    #[test]
    fn test_bridge_address_correct() {
        let address = calc_create_address(&PRECOMPILE_ROLLUP_BRIDGE_DEPLOYER, 1);
        assert_eq!(address, PRECOMPILE_ROLLUP_BRIDGE);
    }

    #[test]
    fn test_create_address() {
        const TESTS: [(Address, u32); 6] = [
            (address!("0000000000000000000000000000000000000000"), 0),
            (
                address!("0000000000000000000000000000000000000000"),
                u32::MIN,
            ),
            (
                address!("0000000000000000000000000000000000000000"),
                u32::MAX,
            ),
            (address!("2340820934820934820934809238402983400000"), 0),
            (
                address!("2340820934820934820934809238402983400000"),
                u32::MIN,
            ),
            (
                address!("2340820934820934820934809238402983400000"),
                u32::MAX,
            ),
        ];
        for (address, nonce) in TESTS {
            assert_eq!(
                calc_create_address(&address, nonce as u64),
                address.create(nonce as u64)
            );
        }
    }

    #[test]
    fn test_create2_address() {
        let address = Address::ZERO;
        let (salt, hash) = (
            b256!("0000000000000000000000000000000000000000000000000000000000000001"),
            b256!("0000000000000000000000000000000000000000000000000000000000000002"),
        );
        assert_eq!(
            calc_create2_address(&address, &salt.into(), &hash),
            address.create2(salt, hash)
        );
    }
}
