use crate::trade_account_deploy::{
    ParallelNonceUsedEvent,
    Secp256k1,
    Signature,
};
use fuels::{
    accounts::{
        signers::private_key::PrivateKeySigner,
        wallet::Unlocked,
    },
    core::{
        codec::*,
        traits::Tokenizable,
    },
    crypto::{
        Message,
        Signature as FuelSignature,
    },
    prelude::*,
    programs::responses::CallResponse,
    types::U256,
};
use sha2::{
    Digest,
    Sha256,
};

pub fn generate_parallel_session_digest<T>(nonce: U256, args: T) -> [u8; 32]
where
    T: Tokenizable,
{
    let bytes = calldata!((nonce, args)).unwrap();
    Sha256::digest(&bytes).into()
}

pub fn generate_parallel_session_signing_payload<T>(nonce: U256, args: T) -> Message
where
    T: Tokenizable,
{
    Message::from_bytes(generate_parallel_session_digest(nonce, args))
}

pub async fn create_parallel_signature_args<T>(
    wallet: &Wallet<Unlocked<PrivateKeySigner>>,
    nonce: U256,
    args: T,
) -> Signature
where
    T: Tokenizable,
{
    let fuel_signature = create_parallel_fuel_signature(wallet, nonce, args).await;
    Signature::Secp256k1(Secp256k1 {
        bits: *fuel_signature,
    })
}

pub async fn create_parallel_fuel_signature<T>(
    wallet: &Wallet<Unlocked<PrivateKeySigner>>,
    nonce: U256,
    args: T,
) -> FuelSignature
where
    T: Tokenizable,
{
    let message = generate_parallel_session_signing_payload(nonce, args);
    wallet.signer().sign(message).await.unwrap()
}

pub fn build_parallel_nonce(
    nonce_session_id: u8,
    timestamp: u64,
    word_position: u128,
    bitmap_position: u8,
) -> U256 {
    U256::from(bitmap_position)
        | (U256::from(word_position) << 8)
        | (U256::from(timestamp) << 136)
        | (U256::from(nonce_session_id) << 168)
}

pub fn assert_parallel_nonce_used_event<T>(
    tx_result: &CallResponse<T>,
    expected_nonce: U256,
) {
    let logs = tx_result
        .decode_logs_with_type::<ParallelNonceUsedEvent>()
        .unwrap();
    let result_event: &ParallelNonceUsedEvent = logs.first().unwrap();

    assert_eq!(expected_nonce, result_event.nonce);
}
