#![cfg(feature = "runtime-benchmarks")]

use super::*;
use frame_benchmarking::v1::*;

use frame_system::RawOrigin;

pub fn create_account<T: Config>() -> T::AccountId {
    let account_bytes = Vec::from([1u8; 32]);
    T::AccountId::decode(&mut &account_bytes[0..32])
        .expect("32 bytes always construct an AccountId32")
}

pub fn prepare_evm_network<T: Config>(network_id: NetworkIdOf<T>) {
    let network_data = NetworkData {
        chain_name: "Ethereum".into(),
        default_endpoints: vec![b"https://other.endpoint.network.com".to_vec()],
        finality_delay: 69,
        avg_block_speed: 69,
        rate_limit_delay: 69,
        block_distance: 69,
        network_type: ghost_networks::NetworkType::Evm,
        gatekeeper: b"0x4d224452801ACEd8B2F0aebE155379bb5D594381".to_vec(),
        topic_name: b"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef".to_vec(),
        incoming_fee: 0,
        outgoing_fee: 0,
    };

    let _ = T::NetworkDataHandler::register(network_id, network_data.clone());
}

benchmarks! {
    slow_clap {
        let network_id = NetworkIdOf::<T>::default();
        prepare_evm_network::<T>(network_id);

        let minimum_balance = <<T as pallet::Config>::Currency>::minimum_balance();
        let receiver = create_account::<T>();
        let amount = minimum_balance + minimum_balance;

        let session_index = T::ValidatorSet::session_index();
        let transaction_hash = H256::repeat_byte(1u8);

        let authorities = vec![T::AuthorityId::generate_pair(None)];
        let bounded_authorities = WeakBoundedVec::<_, T::MaxAuthorities>::try_from(authorities.clone())
            .map_err(|()| "more than the maximum number of keys provided")?;
        Authorities::<T>::set(&session_index, bounded_authorities);
        let authority_index = 0u32;

        let clap = Clap {
            session_index,
            authority_index,
            transaction_hash,
            block_number: 69,
            removed: false,
            network_id,
            receiver: receiver.clone(),
            amount,
        };
        let args_hash = Pallet::<T>::generate_unique_hash(&clap);

        let authority_id = authorities
            .get(authority_index as usize)
            .expect("first authority should exist");
        let signature = authority_id.sign(&clap.encode())
            .ok_or("couldn't make signature")?;

        let empty_bitmap = BitMap::new(1);
        DisabledAuthorityIndexes::<T>::insert(session_index, empty_bitmap);

        assert_eq!(ApplauseDetails::<T>::get(&session_index, &args_hash).is_none(), true);
    }: _(RawOrigin::None, clap, signature)
    verify {
        assert_eq!(ApplauseDetails::<T>::get(&session_index, &args_hash).is_some(), true);
        assert_eq!(<<T as pallet::Config>::Currency>::total_balance(&receiver), amount);
    }

    commit_block {
        let session_index = T::ValidatorSet::session_index();
        let network_id = NetworkIdOf::<T>::default();
        prepare_evm_network::<T>(network_id);

        let authorities = vec![T::AuthorityId::generate_pair(None)];
        let bounded_authorities = WeakBoundedVec::<_, T::MaxAuthorities>::try_from(authorities.clone())
            .map_err(|()| "more than the maximum number of keys provided")?;
        Authorities::<T>::set(&session_index, bounded_authorities);
        let authority_index = 0u32;

        let block_commitment = BlockCommitment {
            session_index,
            authority_index,
            network_id,
            last_stored_block: 69,
        };

        let authority_id = authorities
            .get(authority_index as usize)
            .expect("first authority should exist");
        let signature = authority_id.sign(&block_commitment.encode())
            .ok_or("couldn't make signature")?;
    }: _(RawOrigin::None, block_commitment, signature)
    verify {
        let current_block_number = <Pallet<T> as BlockNumberProvider>::current_block_number();
        let stored_commitment = BlockCommitments::<T>::get(&network_id)
            .get(&authority_index)
            .cloned()
            .unwrap_or_default();
        assert_eq!(stored_commitment.last_stored_block, 69);
        assert_eq!(stored_commitment.commits, 1);
        assert_eq!(stored_commitment.last_updated, current_block_number);
    }

    try_offend_validators {
        let n in 1 .. T::MaxAuthorities::get();
        let d in 0 .. T::MaxAuthorities::get();

        let session_index = T::ValidatorSet::session_index();
        let mut validators_vec = Vec::new();

        for i in 0..T::MaxAuthorities::get() {
            let v = account("validator", i, 0);
            validators_vec.push(v);
        }

        let validators = WeakBoundedVec::<ValidatorId<T>, T::MaxAuthorities>::try_from(validators_vec)
            .expect("weak bounded vec should be constructed; qed");

        let offence_type = OffenceType::CommitmentOffence;
        let disabled_bitmap = BitMap::new(T::MaxAuthorities::get());
        let mut offence_bitmap = BitMap::new(T::MaxAuthorities::get());
        for i in 0..d {
            offence_bitmap.set(i);
        }
    }: {
        let _ = Pallet::<T>::try_offend_validators(
            &session_index,
            &validators,
            offence_bitmap,
            disabled_bitmap,
            offence_type
        );
    }

    impl_benchmark_test_suite!(
        Pallet,
        crate::mock::new_test_ext(),
        crate::mock::Runtime,
    );
}
