use fuels::{
    core::{
        codec::calldata,
        traits::Tokenizable,
    },
    crypto::Message,
    types::{
        Bits256,
        ChainId,
        Identity,
        U256 as FuelU256,
    },
};

use crate::{
    trade_account::{
        generate_session_signing_payload,
        generate_signing_payload,
    },
    trade_account_deploy::{
        CallContractArg,
        CallContractArgs as TradeAccountCallContractArgs,
        CallParams,
        Domain,
        EIP712Domain,
        Encoding,
        MultiCallContractArgs,
        ParallelCallContractArg,
        ParallelCallContractArgs,
        ParallelMultiCallContractArgs,
        ParallelRevokeArgs,
        ParallelSessionArgs,
        ParallelWithdrawArgs,
        RevokeArgs,
        SRC16Domain,
        Secp256k1,
        SessionArgs,
        SetProxyTargetArgs,
        Signature,
        WithdrawArgs,
    },
};
use ethers_core::{
    types::{
        H160,
        RecoveryMessage,
        Signature as EthSig,
        U256,
        U256 as Ethers256,
        transaction::eip712::EIP712Domain as EVMDomain,
    },
    utils::keccak256,
};

const EVM_ADDRESS_PADDING_LENGTH: usize = 12;

/// Takes the rightmost 20 BYTES of a 32-byte Fuel contract ID.
/// Interprets "rightmost" as the last bytes in the array / hex string.
fn fuel_contract_id_to_evm_address(id: [u8; 32]) -> [u8; 20] {
    let mut addr = [0u8; 20];
    // last 20 bytes: indices 12..32
    addr.copy_from_slice(&id[12..32]);
    addr
}

pub trait DomainExt {
    fn separator(&self) -> [u8; 32];
    fn encode<T>(&self, struct_type: T) -> [u8; 32]
    where
        T: SRC16Encode;
}

impl DomainExt for Domain {
    fn separator(&self) -> [u8; 32] {
        match self {
            Domain::EIP712Domain(d) => EVMDomain::from(d.clone()).separator(),
            Domain::SRC16Domain(d) => d.separator(),
        }
    }

    fn encode<T>(&self, struct_type: T) -> [u8; 32]
    where
        T: SRC16Encode,
    {
        let ds = self.separator();
        let encoding = match self {
            Domain::SRC16Domain(_) => Encoding::SRC16,
            Domain::EIP712Domain(_) => Encoding::EIP712,
        };
        let sh = struct_type.struct_hash(encoding);

        let mut preimage = Vec::with_capacity(2 + 32 + 32);
        preimage.extend_from_slice(&[0x19, 0x01]);
        preimage.extend_from_slice(ds.as_slice());
        preimage.extend_from_slice(sh.as_slice());

        keccak256(preimage)
    }
}

impl SRC16Domain {
    // Compute the domain separator;
    pub fn separator(&self) -> [u8; 32] {
        let mut tokens: Vec<u8> = Vec::new();

        tokens.extend_from_slice(src16_domain_type_hash(self).as_ref());

        if self.name.is_some() {
            tokens.extend_from_slice(keccak256(self.name.clone().unwrap()).as_slice());
        }
        if self.version.is_some() {
            tokens.extend_from_slice(keccak256(self.version.clone().unwrap()).as_slice());
        }

        if let Some(chain_id) = self.chain_id {
            tokens.extend(chain_id.0.iter().flat_map(|x| x.to_be_bytes()));
        }

        if let Some(verifying_contract) = self.verifying_contract {
            tokens.extend_from_slice(verifying_contract.as_slice());
        }

        if let Some(salt) = self.salt {
            tokens.extend_from_slice(salt.0.as_slice());
        }

        keccak256(tokens)
    }
}

fn src16_domain_type_hash(domain: &SRC16Domain) -> [u8; 32] {
    let mut fields = Vec::new();

    // IMPORTANT: fixed order
    if domain.name.is_some() {
        fields.push("string name");
    }
    if domain.version.is_some() {
        fields.push("string version");
    }
    if domain.chain_id.is_some() {
        fields.push("u256 chain_id");
    }
    if domain.verifying_contract.is_some() {
        fields.push("contractId verifying_contract");
    }
    if domain.salt.is_some() {
        fields.push("b256 salt");
    }

    let type_string = format!("SRC16Domain({})", fields.join(","));

    keccak256(type_string.as_bytes())
}

fn fuel_u256_to_bytes(value: &FuelU256) -> [u8; 32] {
    let mut bytes = [0u8; 32];
    value.to_big_endian(&mut bytes);
    bytes
}

impl From<EIP712Domain> for EVMDomain {
    fn from(domain: EIP712Domain) -> Self {
        let chain_id = domain.chain_id.map(|id| Ethers256(id.0));
        let verifying_contract = domain
            .verifying_contract
            .map(|contract| H160(fuel_contract_id_to_evm_address(contract.0)));
        let salt = domain.salt.map(|s| s.0);

        EVMDomain {
            name: domain.name,
            version: domain.version,
            chain_id,
            verifying_contract,
            salt,
        }
    }
}

pub trait SRC16Encode {
    #[allow(dead_code)]
    fn type_hash(encoding: Encoding) -> [u8; 32];
    fn struct_hash(self, encoding: Encoding) -> [u8; 32];
}

fn from_compact_or_standard(bytes: &[u8]) -> anyhow::Result<EthSig> {
    match bytes.len() {
        65 => Ok(EthSig::try_from(bytes)?),
        64 => {
            let r = U256::from_big_endian(&bytes[..32]);
            let mut s_bytes = [0u8; 32];
            s_bytes.copy_from_slice(&bytes[32..64]);

            let y_parity = (s_bytes[0] >> 7) & 1;
            // In compact ECDSA signatures, the y-parity is encoded in the most significant bit
            // of the first byte of the s value. This bit must be cleared to obtain the actual s value.
            s_bytes[0] &= 0x7F;

            let s = U256::from_big_endian(&s_bytes);
            // Ethereum uses v = 27 or 28 for legacy signatures; here, we add 27 to the parity bit.
            let v = 27 + y_parity as u64;

            Ok(EthSig { r, s, v })
        }
        n => anyhow::bail!("invalid length {n}, expected 64 or 65"),
    }
}

pub trait SignatureCurve {
    type Address;

    /// Verify that the owner signed the payload
    fn verify_owner<T>(
        &self,
        nonce: u64,
        chain_id: ChainId,
        f_name: String,
        args: Option<T>,
        address: &Self::Address,
    ) -> anyhow::Result<()>
    where
        T: Tokenizable;

    /// Verify that the session key signed the payload
    fn verify_session<T>(
        &self,
        nonce: u64,
        args: T,
        address: &Self::Address,
    ) -> anyhow::Result<()>
    where
        T: Tokenizable;

    fn verify_typed<T>(
        &self,
        args: T,
        domain: &Domain,
        address: &Self::Address,
    ) -> anyhow::Result<()>
    where
        T: SRC16Encode;
}

pub struct Secp256k1Address(pub fuels::types::Address);

impl SignatureCurve for Secp256k1 {
    type Address = Secp256k1Address;

    fn verify_owner<T>(
        &self,
        nonce: u64,
        chain_id: ChainId,
        f_name: String,
        args: Option<T>,
        address: &Self::Address,
    ) -> anyhow::Result<()>
    where
        T: Tokenizable,
    {
        let address = &address.0;

        // if the first 12 bytes of the address are all zeros, then we assume it
        // is an EVM (Ethereum) address and signature
        if address.starts_with([0u8; EVM_ADDRESS_PADDING_LENGTH].as_ref()) {
            let evm_address = &address[EVM_ADDRESS_PADDING_LENGTH..];
            let evm_message = if let Some(args_some) = args {
                calldata!((nonce, *chain_id, f_name, args_some))?
            } else {
                calldata!((nonce, *chain_id, f_name))?
            };
            let evm_sig = from_compact_or_standard(&self.bits)?;
            let recovered_address = evm_sig.recover(&evm_message[..])?;

            if recovered_address.as_bytes() != evm_address {
                anyhow::bail!("invalid evm address provided");
            }

            evm_sig.verify(evm_message, recovered_address)?;
        } else {
            let fuel_message = generate_signing_payload(nonce, *chain_id, f_name, args);
            let fuels_sig = fuels::crypto::Signature::from_bytes(self.bits);
            let recovered_pub_key = fuels_sig.recover(&fuel_message)?;

            if *recovered_pub_key.hash() != address.as_ref() {
                anyhow::bail!("invalid fuel address provided");
            }

            fuels_sig.verify(&recovered_pub_key, &fuel_message)?;
        }

        Ok(())
    }

    fn verify_session<T>(
        &self,
        nonce: u64,
        args: T,
        address: &Self::Address,
    ) -> anyhow::Result<()>
    where
        T: Tokenizable,
    {
        let address = &address.0;
        let fuel_message = generate_session_signing_payload(nonce, args);

        let fuels_sig = fuels::crypto::Signature::from_bytes(self.bits);
        let recovered_pub_key = fuels_sig.recover(&fuel_message)?;

        if *recovered_pub_key.hash() != address.as_ref() {
            anyhow::bail!("invalid fuel address provided");
        }

        fuels_sig.verify(&recovered_pub_key, &fuel_message)?;

        Ok(())
    }

    fn verify_typed<T>(
        &self,
        args: T,
        domain: &Domain,
        address: &Self::Address,
    ) -> anyhow::Result<()>
    where
        T: SRC16Encode,
    {
        let address = &address.0;

        // Encode the typed data
        let message: Message = Message::from_bytes(domain.encode(args));

        // Check if this is an EVM address or Fuel address based on padding
        if address.starts_with([0u8; EVM_ADDRESS_PADDING_LENGTH].as_ref()) {
            // EVM (Ethereum) address verification
            let evm_address = &address[EVM_ADDRESS_PADDING_LENGTH..];
            let evm_sig = from_compact_or_standard(&self.bits)?;
            let recovered_address =
                evm_sig.recover(RecoveryMessage::Hash((*message).into()))?;

            if recovered_address.as_bytes() != evm_address {
                anyhow::bail!("invalid evm address provided");
            }

            evm_sig
                .verify(RecoveryMessage::Hash((*message).into()), recovered_address)?;
        } else {
            // Fuel address verification
            let fuels_sig = fuels::crypto::Signature::from_bytes(self.bits);
            let recovered_pub_key = fuels_sig.recover(&message)?;

            if *recovered_pub_key.hash() != address.as_ref() {
                anyhow::bail!("invalid fuel address provided");
            }

            fuels_sig.verify(&recovered_pub_key, &message)?;
        }

        Ok(())
    }
}

pub enum Address {
    Secp256k1(Secp256k1Address),
    Secp256r1,
    Ed25519,
}

impl From<fuels::types::Address> for Address {
    fn from(addr: fuels::types::Address) -> Self {
        Address::Secp256k1(Secp256k1Address(addr))
    }
}

impl Signature {
    pub fn verify_owner<T>(
        &self,
        nonce: u64,
        chain_id: ChainId,
        f_name: String,
        args: Option<T>,
        address: &Address,
    ) -> anyhow::Result<()>
    where
        T: Tokenizable,
    {
        match (self, address) {
            (Signature::Secp256k1(sig), Address::Secp256k1(addr)) => {
                sig.verify_owner(nonce, chain_id, f_name, args, addr)
            }
            (Signature::Secp256r1(_), Address::Secp256r1) => {
                anyhow::bail!(
                    "owner signature verification not supported for p256 signatures"
                )
            }
            (Signature::Ed25519(_), Address::Ed25519) => {
                anyhow::bail!(
                    "owner signature verification not supported for ed25519 signatures"
                )
            }
            _ => anyhow::bail!("signature and address type mismatch"),
        }
    }

    pub fn verify_session<T>(
        &self,
        nonce: u64,
        args: T,
        address: &Address,
    ) -> anyhow::Result<()>
    where
        T: Tokenizable,
    {
        match (self, address) {
            (Signature::Secp256k1(sig), Address::Secp256k1(addr)) => {
                sig.verify_session(nonce, args, addr)
            }
            (Signature::Secp256r1(_), Address::Secp256r1) => {
                anyhow::bail!(
                    "session signature verification not supported for p256 signatures"
                )
            }
            (Signature::Ed25519(_), Address::Ed25519) => {
                anyhow::bail!(
                    "session signature verification not supported for ed25519 signatures"
                )
            }
            _ => anyhow::bail!("signature and address type mismatch"),
        }
    }

    pub fn verify_typed<T>(
        &self,
        args: T,
        domain: &Domain,
        address: &Address,
    ) -> anyhow::Result<()>
    where
        T: SRC16Encode,
    {
        match (self, address) {
            (Signature::Secp256k1(sig), Address::Secp256k1(addr)) => {
                sig.verify_typed(args, domain, addr)
            }
            (Signature::Secp256r1(_), Address::Secp256r1) => {
                anyhow::bail!(
                    "typed signature verification not supported for p256 signatures"
                )
            }
            (Signature::Ed25519(_), Address::Ed25519) => {
                anyhow::bail!(
                    "typed signature verification not supported for ed25519 signatures"
                )
            }
            _ => anyhow::bail!("signature and address type mismatch"),
        }
    }
}

/// The Keccak256 hash of the type SessionArgs as UTF8 encoded bytes.
///
/// "SessionArgs(u64 nonce,b256 session_id,u64 expiry,contractId[] contract_ids)"
pub const SRC_16_SIGNED_SESSION_TYPE_HASH: &str =
    "0x2eb25d47a049c7084035785d680582656395f354f9484e7e3bbb2993368e46b7";

/// The Keccak256 hash of the type SessionArgs as UTF8 encoded bytes.
///
/// "SessionArgs(uint64 nonce,bytes32 session_id,uint64 expiry,bytes32[] contract_ids)"
pub const EIP_712_SIGNED_SESSION_TYPE_HASH: &str =
    "0xf91528e233badfd402ad99c4d1dd9d109ec218093c9dcf7432699f5bb94721e2";

impl SRC16Encode for SessionArgs {
    fn type_hash(encoding: Encoding) -> [u8; 32] {
        match encoding {
            Encoding::SRC16 => {
                Bits256::from_hex_str(SRC_16_SIGNED_SESSION_TYPE_HASH)
                    .unwrap()
                    .0
            }
            Encoding::EIP712 => {
                Bits256::from_hex_str(EIP_712_SIGNED_SESSION_TYPE_HASH)
                    .unwrap()
                    .0
            }
        }
    }
    fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
        let mut tokens: Vec<u8> = Vec::new();
        match encoding {
            Encoding::SRC16 => tokens.extend_from_slice(
                Bits256::from_hex_str(SRC_16_SIGNED_SESSION_TYPE_HASH)
                    .unwrap()
                    .0
                    .as_slice(),
            ),
            Encoding::EIP712 => tokens.extend_from_slice(
                Bits256::from_hex_str(EIP_712_SIGNED_SESSION_TYPE_HASH)
                    .unwrap()
                    .0
                    .as_slice(),
            ),
        }

        // nonce u64
        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(self.nonce.to_be_bytes().as_slice());
        // session id b256
        match self.session_id {
            Identity::Address(bits) => tokens.extend_from_slice(bits.as_slice()),
            Identity::ContractId(bits) => tokens.extend_from_slice(bits.as_slice()),
        }
        // expiry u64
        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(self.expiry.unix.to_be_bytes().as_slice());

        // contract ids b256[]
        let contract_id_bytes: Vec<u8> = self
            .contract_ids
            .iter()
            .flat_map(|id| id.as_ref())
            .copied()
            .collect();

        tokens.extend_from_slice(keccak256(contract_id_bytes).as_slice());

        keccak256(tokens)
    }
}

/// The Keccak256 hash of the type WithdrawArgs as UTF8 encoded bytes.
///
/// "WithdrawArgs(u64 nonce,identity to,u64 amount,assetId asset_id)"
pub const SRC_16_SIGNED_WITHDRAW_TYPE_HASH: &str =
    "0x9732cd3b83bcf964465644c9c8546dff5d9f2e5c16c077dba9a2a2bc2b9f7188";

/// The Keccak256 hash of the type WithdrawArgs as UTF8 encoded bytes.
///
/// "WithdrawArgs(uint64 nonce,bytes32 to,uint64 amount,bytes32 asset_id)"
pub const EIP_712_SIGNED_WITHDRAW_TYPE_HASH: &str =
    "0x138ab031e51a15ab8c1d21d9a203f6cc5c134d7dbb44a9fd3f6acc3231af1c32";

impl SRC16Encode for &WithdrawArgs {
    fn type_hash(encoding: Encoding) -> [u8; 32] {
        match encoding {
            Encoding::SRC16 => {
                Bits256::from_hex_str(SRC_16_SIGNED_WITHDRAW_TYPE_HASH)
                    .unwrap()
                    .0
            }
            Encoding::EIP712 => {
                Bits256::from_hex_str(EIP_712_SIGNED_WITHDRAW_TYPE_HASH)
                    .unwrap()
                    .0
            }
        }
    }
    fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
        let mut tokens: Vec<u8> = Vec::new();
        match encoding {
            Encoding::SRC16 => tokens.extend_from_slice(
                Bits256::from_hex_str(SRC_16_SIGNED_WITHDRAW_TYPE_HASH)
                    .unwrap()
                    .0
                    .as_slice(),
            ),
            Encoding::EIP712 => tokens.extend_from_slice(
                Bits256::from_hex_str(EIP_712_SIGNED_WITHDRAW_TYPE_HASH)
                    .unwrap()
                    .0
                    .as_slice(),
            ),
        }

        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(self.nonce.to_be_bytes().as_slice());
        match self.to {
            Identity::Address(bits) => tokens.extend_from_slice(bits.as_slice()),
            Identity::ContractId(bits) => tokens.extend_from_slice(bits.as_slice()),
        }
        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(self.amount.to_be_bytes().as_slice());
        tokens.extend_from_slice(self.asset_id.as_slice());

        keccak256(tokens)
    }
}

/// The Keccak256 hash of the type CallParams as UTF8 encoded bytes.
///
/// "CallParams(u64 coins,assetId asset_id,u64 gas)"
pub const SRC_16_SIGNED_CALL_PARAMS_TYPE_HASH: &str =
    "0x05816efb1220d5dd49f0abf9882712729285e7080da3dc2ba051a6ab701cf3b8";

/// The Keccak256 hash of the type CallParams as UTF8 encoded bytes.
///
/// "CallParams(uint64 coins,bytes32 asset_id,uint64 gas)"
pub const EIP_712_SIGNED_CALL_PARAMS_TYPE_HASH: &str =
    "0x322b030cfd61eddd3d0acc5c37358539477197bfc2599dc09a9c75ad47f6e5b8";

impl SRC16Encode for CallParams {
    fn type_hash(encoding: Encoding) -> [u8; 32] {
        match encoding {
            Encoding::SRC16 => {
                Bits256::from_hex_str(SRC_16_SIGNED_CALL_PARAMS_TYPE_HASH)
                    .unwrap()
                    .0
            }
            Encoding::EIP712 => {
                Bits256::from_hex_str(EIP_712_SIGNED_CALL_PARAMS_TYPE_HASH)
                    .unwrap()
                    .0
            }
        }
    }
    fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
        let mut tokens: Vec<u8> = Vec::new();
        match encoding {
            Encoding::SRC16 => tokens.extend_from_slice(
                Bits256::from_hex_str(SRC_16_SIGNED_CALL_PARAMS_TYPE_HASH)
                    .unwrap()
                    .0
                    .as_slice(),
            ),
            Encoding::EIP712 => tokens.extend_from_slice(
                Bits256::from_hex_str(EIP_712_SIGNED_CALL_PARAMS_TYPE_HASH)
                    .unwrap()
                    .0
                    .as_slice(),
            ),
        }

        // Call Params
        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(self.coins.to_be_bytes().as_slice());
        tokens.extend_from_slice(self.asset_id.as_slice());
        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(self.gas.to_be_bytes().as_slice());

        keccak256(tokens)
    }
}

/// The Keccak256 hash of the type CallContractArg as UTF8 encoded bytes.
///
/// "CallContractArg(contractId contract_id,bytes function_selector,CallParams call_params,bytes call_data)CallParams(u64 coins,assetId asset_id,u64 gas)"
pub const SRC_16_SIGNED_CALL_CONTRACT_ARG_TYPE_HASH: &str =
    "0xc8ac163d1f87886f48788901b8e5eb4eebb3edad4ae111859b2a051059920048";

/// The Keccak256 hash of the type CallContractArg as UTF8 encoded bytes.
///
/// "CallContractArg(bytes32 contract_id,bytes function_selector,CallParams call_params,bytes call_data)CallParams(uint64 coins,bytes32 asset_id,uint64 gas)"
pub const EIP_712_SIGNED_CALL_CONTRACT_ARG_TYPE_HASH: &str =
    "0x325b44fd679189ccd8f708a440a27448cbfd6f47f90205e8ace3f1efc27c92d6";

impl SRC16Encode for CallContractArg {
    fn type_hash(encoding: Encoding) -> [u8; 32] {
        match encoding {
            Encoding::SRC16 => {
                Bits256::from_hex_str(SRC_16_SIGNED_CALL_CONTRACT_ARG_TYPE_HASH)
                    .unwrap()
                    .0
            }
            Encoding::EIP712 => {
                Bits256::from_hex_str(EIP_712_SIGNED_CALL_CONTRACT_ARG_TYPE_HASH)
                    .unwrap()
                    .0
            }
        }
    }
    fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
        let mut tokens: Vec<u8> = Vec::new();
        match encoding {
            Encoding::SRC16 => tokens.extend_from_slice(
                Bits256::from_hex_str(SRC_16_SIGNED_CALL_CONTRACT_ARG_TYPE_HASH)
                    .unwrap()
                    .0
                    .as_slice(),
            ),
            Encoding::EIP712 => tokens.extend_from_slice(
                Bits256::from_hex_str(EIP_712_SIGNED_CALL_CONTRACT_ARG_TYPE_HASH)
                    .unwrap()
                    .0
                    .as_slice(),
            ),
        }

        // Contract Id
        tokens.extend_from_slice(self.contract_id.as_slice());

        // Function selector
        tokens.extend_from_slice(keccak256(self.function_selector.0).as_slice());

        // Call Params
        tokens.extend_from_slice(self.call_params.struct_hash(encoding).as_slice());

        // Call data
        match self.call_data {
            Some(data) => tokens.extend_from_slice(keccak256(data.0).as_slice()),
            None => tokens.extend_from_slice(keccak256(Vec::new()).as_slice()),
        }

        keccak256(tokens)
    }
}

/// The Keccak256 hash of the type CallContractArgs as UTF8 encoded bytes.
///
/// "CallContractArgs(u64 nonce,CallContractArg call_contract_args)CallContractArg(contractId contract_id,bytes function_selector,CallParams call_params,bytes call_data)CallParams(u64 coins,assetId asset_id,u64 gas)"
pub const SRC_16_SIGNED_CALL_CONTRACT_TYPE_HASH: &str =
    "0x3f9265dacb0b84ea31e56285f5757a2200e743f27f0b27c3d58db09c9f62cb46";

/// The Keccak256 hash of the type CallContractArgs as UTF8 encoded bytes.
///
/// "CallContractArgs(uint64 nonce,CallContractArg call_contract_args)CallContractArg(bytes32 contract_id,bytes function_selector,CallParams call_params,bytes call_data)CallParams(uint64 coins,bytes32 asset_id,uint64 gas)"
pub const EIP_712_SIGNED_CALL_CONTRACT_TYPE_HASH: &str =
    "0x26ade4c76b3df03eed7d7a2d1387896e8717f8a862414199904b8a4455b25699";

impl SRC16Encode for TradeAccountCallContractArgs {
    fn type_hash(encoding: Encoding) -> [u8; 32] {
        match encoding {
            Encoding::SRC16 => {
                Bits256::from_hex_str(SRC_16_SIGNED_CALL_CONTRACT_TYPE_HASH)
                    .unwrap()
                    .0
            }
            Encoding::EIP712 => {
                Bits256::from_hex_str(EIP_712_SIGNED_CALL_CONTRACT_TYPE_HASH)
                    .unwrap()
                    .0
            }
        }
    }
    fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
        let mut tokens: Vec<u8> = Vec::new();
        match encoding {
            Encoding::SRC16 => tokens.extend_from_slice(
                Bits256::from_hex_str(SRC_16_SIGNED_CALL_CONTRACT_TYPE_HASH)
                    .unwrap()
                    .0
                    .as_slice(),
            ),
            Encoding::EIP712 => tokens.extend_from_slice(
                Bits256::from_hex_str(EIP_712_SIGNED_CALL_CONTRACT_TYPE_HASH)
                    .unwrap()
                    .0
                    .as_slice(),
            ),
        }

        // Nonce
        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(self.nonce.to_be_bytes().as_slice());

        // Call Contract args
        tokens
            .extend_from_slice(self.call_contract_args.struct_hash(encoding).as_slice());

        keccak256(tokens)
    }
}

/// The Keccak256 hash of the type MultiCallContractArgs as UTF8 encoded bytes.
///
/// "MultiCallContractArgs(u64 nonce,CallContractArg[] call_contract_args)CallContractArg(contractId contract_id,bytes function_selector,CallParams call_params,bytes call_data)CallParams(u64 coins,assetId asset_id,u64 gas)"
pub const SRC_16_SIGNED_MULTI_CALL_CONTRACT_TYPE_HASH: &str =
    "0x44c30cb1e12dbc44e06a72b087359f9af5caae554d43dada454d5f6619eabf5f";

/// The Keccak256 hash of the type MultiCallContractArgs as UTF8 encoded bytes.
///
/// "MultiCallContractArgs(uint64 nonce,CallContractArg[] call_contract_args)CallContractArg(bytes32 contract_id,bytes function_selector,CallParams call_params,bytes call_data)CallParams(uint64 coins,bytes32 asset_id,uint64 gas)"
pub const EIP_712_SIGNED_MULTI_CALL_CONTRACT_TYPE_HASH: &str =
    "0x292b2913cef817f041ea01e3b02c4dd2b7184e6490f21a84ece8443e406640a1";

impl SRC16Encode for MultiCallContractArgs {
    fn type_hash(encoding: Encoding) -> [u8; 32] {
        match encoding {
            Encoding::SRC16 => {
                Bits256::from_hex_str(SRC_16_SIGNED_MULTI_CALL_CONTRACT_TYPE_HASH)
                    .unwrap()
                    .0
            }
            Encoding::EIP712 => {
                Bits256::from_hex_str(EIP_712_SIGNED_MULTI_CALL_CONTRACT_TYPE_HASH)
                    .unwrap()
                    .0
            }
        }
    }
    fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
        let mut tokens: Vec<u8> = Vec::new();
        match encoding {
            Encoding::SRC16 => tokens.extend_from_slice(
                Bits256::from_hex_str(SRC_16_SIGNED_MULTI_CALL_CONTRACT_TYPE_HASH)
                    .unwrap()
                    .0
                    .as_slice(),
            ),
            Encoding::EIP712 => tokens.extend_from_slice(
                Bits256::from_hex_str(EIP_712_SIGNED_MULTI_CALL_CONTRACT_TYPE_HASH)
                    .unwrap()
                    .0
                    .as_slice(),
            ),
        }

        // Nonce
        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(self.nonce.to_be_bytes().as_slice());

        // Contract call params
        let mut call_buffer: Vec<u8> = Vec::new();
        for call in self.call_contract_args {
            let h = call.struct_hash(encoding.clone());
            call_buffer.extend_from_slice(h.as_slice());
        }
        tokens.extend_from_slice(keccak256(call_buffer.as_slice()).as_slice());

        keccak256(tokens)
    }
}

/// The Keccak256 hash of the type RevokeArgs as UTF8 encoded bytes.
///
/// "RevokeArgs(u64 nonce)"
pub const SRC_16_REVOKE_TYPE_HASH: &str =
    "0x97c9c74c84314e9fcc4ba97ad28e9cde1eda9b79b0229b54219de0e79560ac86";

/// The Keccak256 hash of the type RevokeArgs as UTF8 encoded bytes.
///
/// "RevokeArgs(uint64 nonce)"
pub const EIP_712_REVOKE_TYPE_HASH: &str =
    "0xf863f4b733d291f6bd744df3ba070a394dd83fced6f038a22195c1b34009dabb";

impl SRC16Encode for RevokeArgs {
    fn type_hash(encoding: Encoding) -> [u8; 32] {
        match encoding {
            Encoding::SRC16 => Bits256::from_hex_str(SRC_16_REVOKE_TYPE_HASH).unwrap().0,
            Encoding::EIP712 => {
                Bits256::from_hex_str(EIP_712_REVOKE_TYPE_HASH).unwrap().0
            }
        }
    }
    fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
        let mut tokens: Vec<u8> = Vec::new();
        match encoding {
            Encoding::SRC16 => tokens.extend_from_slice(
                Bits256::from_hex_str(SRC_16_REVOKE_TYPE_HASH)
                    .unwrap()
                    .0
                    .as_slice(),
            ),
            Encoding::EIP712 => tokens.extend_from_slice(
                Bits256::from_hex_str(EIP_712_REVOKE_TYPE_HASH)
                    .unwrap()
                    .0
                    .as_slice(),
            ),
        }

        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(self.nonce.to_be_bytes().as_slice());

        keccak256(tokens)
    }
}

/// The Keccak256 hash of the type SetProxyTargetArgs as UTF8 encoded bytes.
///
/// "SetProxyTargetArgs(u64 nonce)"
pub const SRC_16_PROXY_TARGET_TYPE_HASH: &str =
    "0x523db1f70cbdfc0982627ed4e52dc429384c32e94e16ddc5fa5ab921035d8d6d";

/// The Keccak256 hash of the type RevokeArgs as UTF8 encoded bytes.
///
/// "SetProxyTargetArgs(uint64 nonce)"
pub const EIP_712_PROXY_TARGET_TYPE_HASH: &str =
    "0xf12c7a18e931f36d11f09bd52f37183c4993a3a509ac0c2c6cec459cee7a004d";

impl SRC16Encode for SetProxyTargetArgs {
    fn type_hash(encoding: Encoding) -> [u8; 32] {
        match encoding {
            Encoding::SRC16 => {
                Bits256::from_hex_str(SRC_16_PROXY_TARGET_TYPE_HASH)
                    .unwrap()
                    .0
            }
            Encoding::EIP712 => {
                Bits256::from_hex_str(EIP_712_PROXY_TARGET_TYPE_HASH)
                    .unwrap()
                    .0
            }
        }
    }
    fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
        let mut tokens: Vec<u8> = Vec::new();
        match encoding {
            Encoding::SRC16 => tokens.extend_from_slice(
                Bits256::from_hex_str(SRC_16_PROXY_TARGET_TYPE_HASH)
                    .unwrap()
                    .0
                    .as_slice(),
            ),
            Encoding::EIP712 => tokens.extend_from_slice(
                Bits256::from_hex_str(EIP_712_PROXY_TARGET_TYPE_HASH)
                    .unwrap()
                    .0
                    .as_slice(),
            ),
        }

        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(self.nonce.to_be_bytes().as_slice());

        keccak256(tokens)
    }
}

pub const SRC_16_PARALLEL_SIGNED_SESSION_TYPE_HASH: &str =
    "0x7bcb65ebf5b9c6a7eb6a5d92462eeb2b3eba824e9c8f22c2b01533c2e3364019";

pub const EIP_712_PARALLEL_SIGNED_SESSION_TYPE_HASH: &str =
    "0x67a8adc215ea9fc6e3a50e038c7c123758629a96e6f33c3a5b6bd8534842d9a7";

impl SRC16Encode for &ParallelSessionArgs {
    fn type_hash(encoding: Encoding) -> [u8; 32] {
        match encoding {
            Encoding::SRC16 => {
                Bits256::from_hex_str(SRC_16_PARALLEL_SIGNED_SESSION_TYPE_HASH)
                    .unwrap()
                    .0
            }
            Encoding::EIP712 => {
                Bits256::from_hex_str(EIP_712_PARALLEL_SIGNED_SESSION_TYPE_HASH)
                    .unwrap()
                    .0
            }
        }
    }

    fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
        let mut tokens: Vec<u8> = Vec::new();
        match encoding {
            Encoding::SRC16 => tokens.extend_from_slice(
                Bits256::from_hex_str(SRC_16_PARALLEL_SIGNED_SESSION_TYPE_HASH)
                    .unwrap()
                    .0
                    .as_slice(),
            ),
            Encoding::EIP712 => tokens.extend_from_slice(
                Bits256::from_hex_str(EIP_712_PARALLEL_SIGNED_SESSION_TYPE_HASH)
                    .unwrap()
                    .0
                    .as_slice(),
            ),
        }

        tokens.extend_from_slice(fuel_u256_to_bytes(&self.nonce).as_slice());
        match self.session_id {
            Identity::Address(bits) => tokens.extend_from_slice(bits.as_slice()),
            Identity::ContractId(bits) => tokens.extend_from_slice(bits.as_slice()),
        }
        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(self.expiry.unix.to_be_bytes().as_slice());

        let contract_id_bytes: Vec<u8> = self
            .contract_ids
            .iter()
            .flat_map(|id| id.as_ref())
            .copied()
            .collect();
        tokens.extend_from_slice(keccak256(contract_id_bytes).as_slice());

        keccak256(tokens)
    }
}

pub const SRC_16_PARALLEL_SIGNED_WITHDRAW_TYPE_HASH: &str =
    "0x95a9c4c23dd3607b14ecdc67fcc42946f61a4f1d153dc7dfbd77ca6c2af1c6f6";

pub const EIP_712_PARALLEL_SIGNED_WITHDRAW_TYPE_HASH: &str =
    "0x7d2e70c357f073fd00c187b1871bf74c440ec8303b435f001d884d6ffd77ca1c";

impl SRC16Encode for &ParallelWithdrawArgs {
    fn type_hash(encoding: Encoding) -> [u8; 32] {
        match encoding {
            Encoding::SRC16 => {
                Bits256::from_hex_str(SRC_16_PARALLEL_SIGNED_WITHDRAW_TYPE_HASH)
                    .unwrap()
                    .0
            }
            Encoding::EIP712 => {
                Bits256::from_hex_str(EIP_712_PARALLEL_SIGNED_WITHDRAW_TYPE_HASH)
                    .unwrap()
                    .0
            }
        }
    }

    fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
        let mut tokens: Vec<u8> = Vec::new();
        match encoding {
            Encoding::SRC16 => tokens.extend_from_slice(
                Bits256::from_hex_str(SRC_16_PARALLEL_SIGNED_WITHDRAW_TYPE_HASH)
                    .unwrap()
                    .0
                    .as_slice(),
            ),
            Encoding::EIP712 => tokens.extend_from_slice(
                Bits256::from_hex_str(EIP_712_PARALLEL_SIGNED_WITHDRAW_TYPE_HASH)
                    .unwrap()
                    .0
                    .as_slice(),
            ),
        }

        tokens.extend_from_slice(fuel_u256_to_bytes(&self.nonce).as_slice());
        match self.to {
            Identity::Address(bits) => tokens.extend_from_slice(bits.as_slice()),
            Identity::ContractId(bits) => tokens.extend_from_slice(bits.as_slice()),
        }
        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
        tokens.extend_from_slice(self.amount.to_be_bytes().as_slice());
        tokens.extend_from_slice(self.asset_id.as_slice());

        keccak256(tokens)
    }
}

pub const SRC_16_PARALLEL_SIGNED_CALL_CONTRACT_ARG_TYPE_HASH: &str =
    "0x4b0849aa7446711dce1e67cef7c4fb412bf532bcef8e55645b311d5fe670e430";

pub const EIP_712_PARALLEL_SIGNED_CALL_CONTRACT_ARG_TYPE_HASH: &str =
    "0x06588b3fa3b32122be787ca84fefaf67ae00491f90a3333b0f1a6bf23991d21b";

impl SRC16Encode for ParallelCallContractArg {
    fn type_hash(encoding: Encoding) -> [u8; 32] {
        match encoding {
            Encoding::SRC16 => {
                Bits256::from_hex_str(SRC_16_PARALLEL_SIGNED_CALL_CONTRACT_ARG_TYPE_HASH)
                    .unwrap()
                    .0
            }
            Encoding::EIP712 => {
                Bits256::from_hex_str(EIP_712_PARALLEL_SIGNED_CALL_CONTRACT_ARG_TYPE_HASH)
                    .unwrap()
                    .0
            }
        }
    }

    fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
        let mut tokens: Vec<u8> = Vec::new();
        match encoding {
            Encoding::SRC16 => tokens.extend_from_slice(
                Bits256::from_hex_str(SRC_16_PARALLEL_SIGNED_CALL_CONTRACT_ARG_TYPE_HASH)
                    .unwrap()
                    .0
                    .as_slice(),
            ),
            Encoding::EIP712 => tokens.extend_from_slice(
                Bits256::from_hex_str(
                    EIP_712_PARALLEL_SIGNED_CALL_CONTRACT_ARG_TYPE_HASH,
                )
                .unwrap()
                .0
                .as_slice(),
            ),
        }

        tokens.extend_from_slice(self.contract_id.as_slice());
        tokens.extend_from_slice(keccak256(self.function_selector.0).as_slice());
        tokens.extend_from_slice(self.call_params.struct_hash(encoding).as_slice());
        match self.call_data {
            Some(data) => tokens.extend_from_slice(keccak256(data.0).as_slice()),
            None => tokens.extend_from_slice(keccak256(Vec::new()).as_slice()),
        }

        keccak256(tokens)
    }
}

pub const SRC_16_PARALLEL_SIGNED_CALL_CONTRACT_TYPE_HASH: &str =
    "0x0715266d50809ef56e0c9daa18526acc618a197db7485b2f4291450bfce0c343";

pub const EIP_712_PARALLEL_SIGNED_CALL_CONTRACT_TYPE_HASH: &str =
    "0xba28dc067be2d8fec20d75fc6de7c3e3187ac4af33d8f8bf39992ac745808544";

impl SRC16Encode for ParallelCallContractArgs {
    fn type_hash(encoding: Encoding) -> [u8; 32] {
        match encoding {
            Encoding::SRC16 => {
                Bits256::from_hex_str(SRC_16_PARALLEL_SIGNED_CALL_CONTRACT_TYPE_HASH)
                    .unwrap()
                    .0
            }
            Encoding::EIP712 => {
                Bits256::from_hex_str(EIP_712_PARALLEL_SIGNED_CALL_CONTRACT_TYPE_HASH)
                    .unwrap()
                    .0
            }
        }
    }

    fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
        let mut tokens: Vec<u8> = Vec::new();
        match encoding {
            Encoding::SRC16 => tokens.extend_from_slice(
                Bits256::from_hex_str(SRC_16_PARALLEL_SIGNED_CALL_CONTRACT_TYPE_HASH)
                    .unwrap()
                    .0
                    .as_slice(),
            ),
            Encoding::EIP712 => tokens.extend_from_slice(
                Bits256::from_hex_str(EIP_712_PARALLEL_SIGNED_CALL_CONTRACT_TYPE_HASH)
                    .unwrap()
                    .0
                    .as_slice(),
            ),
        }

        tokens.extend_from_slice(fuel_u256_to_bytes(&self.nonce).as_slice());
        tokens
            .extend_from_slice(self.call_contract_args.struct_hash(encoding).as_slice());

        keccak256(tokens)
    }
}

pub const SRC_16_PARALLEL_SIGNED_MULTI_CALL_CONTRACT_TYPE_HASH: &str =
    "0x6e32a14f4ba8a2ed500508d524a5dc852052062ee2323ed1a564d344fffda461";

pub const EIP_712_PARALLEL_SIGNED_MULTI_CALL_CONTRACT_TYPE_HASH: &str =
    "0xc74d58c126fe07446fe4ae31d63e02160ebe265cc44024b5227bacfdb31dd28b";

impl SRC16Encode for ParallelMultiCallContractArgs {
    fn type_hash(encoding: Encoding) -> [u8; 32] {
        match encoding {
            Encoding::SRC16 => {
                Bits256::from_hex_str(
                    SRC_16_PARALLEL_SIGNED_MULTI_CALL_CONTRACT_TYPE_HASH,
                )
                .unwrap()
                .0
            }
            Encoding::EIP712 => {
                Bits256::from_hex_str(
                    EIP_712_PARALLEL_SIGNED_MULTI_CALL_CONTRACT_TYPE_HASH,
                )
                .unwrap()
                .0
            }
        }
    }

    fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
        let mut tokens: Vec<u8> = Vec::new();
        match encoding {
            Encoding::SRC16 => tokens.extend_from_slice(
                Bits256::from_hex_str(
                    SRC_16_PARALLEL_SIGNED_MULTI_CALL_CONTRACT_TYPE_HASH,
                )
                .unwrap()
                .0
                .as_slice(),
            ),
            Encoding::EIP712 => tokens.extend_from_slice(
                Bits256::from_hex_str(
                    EIP_712_PARALLEL_SIGNED_MULTI_CALL_CONTRACT_TYPE_HASH,
                )
                .unwrap()
                .0
                .as_slice(),
            ),
        }

        tokens.extend_from_slice(fuel_u256_to_bytes(&self.nonce).as_slice());

        let mut call_buffer: Vec<u8> = Vec::new();
        for call in self.call_contract_args {
            call_buffer.extend_from_slice(call.struct_hash(encoding.clone()).as_slice());
        }
        tokens.extend_from_slice(keccak256(call_buffer).as_slice());

        keccak256(tokens)
    }
}

pub const SRC_16_PARALLEL_REVOKE_TYPE_HASH: &str =
    "0xac7c930989abf28d71694ae7f327a130cdc1ef80040d94c6873cf0686024382d";

pub const EIP_712_PARALLEL_REVOKE_TYPE_HASH: &str =
    "0x51c1376821126b4be9647fdbbf51877dd5a72c6a76e58fb957f37ac985962ea8";

impl SRC16Encode for ParallelRevokeArgs {
    fn type_hash(encoding: Encoding) -> [u8; 32] {
        match encoding {
            Encoding::SRC16 => {
                Bits256::from_hex_str(SRC_16_PARALLEL_REVOKE_TYPE_HASH)
                    .unwrap()
                    .0
            }
            Encoding::EIP712 => {
                Bits256::from_hex_str(EIP_712_PARALLEL_REVOKE_TYPE_HASH)
                    .unwrap()
                    .0
            }
        }
    }

    fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
        let mut tokens: Vec<u8> = Vec::new();
        match encoding {
            Encoding::SRC16 => tokens.extend_from_slice(
                Bits256::from_hex_str(SRC_16_PARALLEL_REVOKE_TYPE_HASH)
                    .unwrap()
                    .0
                    .as_slice(),
            ),
            Encoding::EIP712 => tokens.extend_from_slice(
                Bits256::from_hex_str(EIP_712_PARALLEL_REVOKE_TYPE_HASH)
                    .unwrap()
                    .0
                    .as_slice(),
            ),
        }

        tokens.extend_from_slice(fuel_u256_to_bytes(&self.nonce).as_slice());

        keccak256(tokens)
    }
}
