#![cfg(test)]

use std::time::{SystemTime, UNIX_EPOCH};

use super::*;
use crate::mock::*;

use frame_support::{assert_err, assert_ok, dispatch};
use sp_core::offchain::{
    testing,
    testing::{TestOffchainExt, TestTransactionPoolExt},
    OffchainDbExt, OffchainWorkerExt, TransactionPoolExt,
};
use sp_runtime::testing::UintAuthorityId;

use ghost_networks::BridgedInflationCurve;
use pallet_staking::EraPayout;

#[test]
fn should_calculate_throttling_slash_function_correctly() {
    let dummy_offence = SlowClapOffence {
        session_index: 0,
        validator_set_count: 69,
        offenders: vec![()],
        offence_type: OffenceType::ThrottlingOffence(Perbill::from_percent(50)),
    };

    assert_eq!(dummy_offence.slash_fraction(1), Perbill::from_percent(50));
    assert_eq!(dummy_offence.slash_fraction(5), Perbill::from_percent(10));
    assert_eq!(dummy_offence.slash_fraction(10), Perbill::from_percent(5));

    let dummy_offence = SlowClapOffence {
        session_index: 0,
        validator_set_count: 100,
        offenders: vec![()],
        offence_type: OffenceType::CommitmentOffence,
    };

    assert_eq!(dummy_offence.slash_fraction(1), Perbill::zero());
    assert_eq!(dummy_offence.slash_fraction(5), Perbill::zero());
    assert_eq!(
        dummy_offence.slash_fraction(15),
        Perbill::from_parts(57_600_000)
    );
    assert_eq!(
        dummy_offence.slash_fraction(20),
        Perbill::from_parts(193_600_000)
    );
    assert_eq!(
        dummy_offence.slash_fraction(25),
        Perbill::from_parts(409_600_000)
    );
    assert_eq!(
        dummy_offence.slash_fraction(30),
        Perbill::from_parts(705_600_000)
    );
    assert_eq!(dummy_offence.slash_fraction(50), Perbill::one());
}

#[test]
fn bitmap_operations_correct() {
    let max_value = 440;
    let sixty_nine = 69;
    let four_twenty = 420;
    let mut bitmap = BitMap::new(max_value);
    assert_eq!(bitmap.max_value(), max_value);

    assert!(!bitmap.exists(&sixty_nine));
    bitmap.set(sixty_nine);
    assert!(bitmap.exists(&sixty_nine));

    assert!(!bitmap.exists(&four_twenty));
    bitmap.set(four_twenty);
    assert!(bitmap.exists(&four_twenty));

    let bitmap_cloned = bitmap.clone();
    let mut bucket_iter = bitmap_cloned.iter_buckets();
    let empty_result = 0;
    assert_eq!(bucket_iter.next(), Some((&0u32, &empty_result)));
    // index = 69 / 64 = 1; bit_pos = 69 % 64 = 5
    let first_result = 1 << 5;
    assert_eq!(bucket_iter.next(), Some((&1u32, &first_result)));
    assert_eq!(bucket_iter.next(), Some((&2u32, &empty_result)));
    assert_eq!(bucket_iter.next(), Some((&3u32, &empty_result)));
    assert_eq!(bucket_iter.next(), Some((&4u32, &empty_result)));
    assert_eq!(bucket_iter.next(), Some((&5u32, &empty_result)));
    // index = 420 / 64 = 6; bit_pos = 420 % 64 = 36
    let second_result = 1 << 36;
    assert_eq!(bucket_iter.next(), Some((&6u32, &second_result)));
    assert_eq!(bucket_iter.next(), None);

    assert_eq!(bitmap.get_bucket(&0), empty_result);
    assert_eq!(bitmap.get_bucket(&1), first_result);
    assert_eq!(bitmap.get_bucket(&6), second_result);

    bitmap.unset(sixty_nine);
    bitmap.unset(four_twenty);

    assert!(!bitmap.exists(&sixty_nine));
    assert!(!bitmap.exists(&four_twenty));

    assert_eq!(BitMap::size_of_bucket(), 6); // for u64

    let mut bitmap1 = BitMap::new(max_value);
    let mut bitmap2 = BitMap::new(max_value);
    assert_eq!(bitmap1.max_value(), max_value);
    assert_eq!(bitmap2.max_value(), max_value);

    bitmap1.set(1);
    bitmap1.set(12);
    bitmap1.set(44);
    bitmap1.set(80);

    bitmap2.set(1);
    bitmap2.set(2);
    bitmap2.set(15);
    bitmap2.set(36);
    bitmap2.set(130);

    let ones = bitmap1.clone().bitor(bitmap2.clone()).count_ones();
    assert_eq!(ones, 8);

    bitmap2.set(80);
    bitmap2.set(420);

    let ones = bitmap1.clone().bitor(bitmap2.clone()).count_ones();
    assert_eq!(ones, 9);

    bitmap1.set(15);
    bitmap1.set(69);
    bitmap1.set(33);
    bitmap2.set(15);
    bitmap2.set(69);
    bitmap2.set(33);

    let ones = bitmap1.clone().bitor(bitmap2.clone()).count_ones();
    assert_eq!(ones, 11);
}

#[test]
fn should_correctly_adjust_to_block() {
    assert_eq!(SlowClap::adjust_to_block(420, 69, 1337), 420);
    assert_eq!(SlowClap::adjust_to_block(420, 1337, 69), 420);
    assert_eq!(SlowClap::adjust_to_block(1337, 420, 69), 489);
    assert_eq!(SlowClap::adjust_to_block(1337, 69, 420), 489);
    assert_eq!(SlowClap::adjust_to_block(69, 1337, 420), 69);
    assert_eq!(SlowClap::adjust_to_block(69, 420, 1337), 69);
}

#[test]
fn request_body_is_correct_for_get_block_number() {
    let (offchain, _) = TestOffchainExt::new();
    let mut t = sp_io::TestExternalities::default();
    t.register_extension(OffchainWorkerExt::new(offchain));

    t.execute_with(|| {
        let network_data = prepare_evm_network(Some(1), None);
        let request_body = SlowClap::prepare_request_body_for_latest_block(&network_data);
        assert_eq!(
            core::str::from_utf8(&request_body).unwrap(),
            r#"{"id":0,"jsonrpc":"2.0","method":"eth_blockNumber"}"#
        );
    });
}

#[test]
fn request_body_is_correct_for_get_logs() {
    let (offchain, _) = TestOffchainExt::new();
    let mut t = sp_io::TestExternalities::default();
    t.register_extension(OffchainWorkerExt::new(offchain));

    t.execute_with(|| {
        let from_block: u64 = 420;
        let to_block: u64 = 1337;
        let network_data = prepare_evm_network(Some(1), None);
        let request_body = SlowClap::prepare_request_body_for_latest_transfers(
            from_block, to_block, &network_data);
        assert_eq!(core::str::from_utf8(&request_body).unwrap(),
            r#"{"id":0,"jsonrpc":"2.0","method":"eth_getLogs","params":[{"fromBlock":"0x1a4","toBlock":"0x539","address":"0x4d224452801ACEd8B2F0aebE155379bb5D594381","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"]}]}"#,
        );
    });
}

#[test]
fn should_make_http_call_for_block_number() {
    let (offchain, state) = TestOffchainExt::new();
    let mut t = sp_io::TestExternalities::default();
    t.register_extension(OffchainWorkerExt::new(offchain));

    evm_block_response(&mut state.write());

    let _: Result<(), OffchainErr<u32>> = t.execute_with(|| {
        let rpc_endpoint = get_rpc_endpoint();
        let network_data = prepare_evm_network(Some(1), None);
        let request_body = SlowClap::prepare_request_body_for_latest_block(&network_data);
        let raw_response = SlowClap::fetch_from_remote(&rpc_endpoint, &request_body)?;
        assert_eq!(raw_response.len(), 45usize); // precalculated
        Ok(())
    });
}

#[test]
fn should_make_http_call_for_logs() {
    let (offchain, state) = TestOffchainExt::new();
    let mut t = sp_io::TestExternalities::default();
    t.register_extension(OffchainWorkerExt::new(offchain));

    evm_logs_response(&mut state.write());

    let _: Result<(), OffchainErr<u32>> = t.execute_with(|| {
        let from_block: u64 = 20335770;
        let to_block: u64 = 20335858;
        let rpc_endpoint = get_rpc_endpoint();

        let network_data = prepare_evm_network(Some(1), None);
        let request_body = SlowClap::prepare_request_body_for_latest_transfers(
            from_block,
            to_block,
            &network_data,
        );
        let raw_response = SlowClap::fetch_from_remote(&rpc_endpoint, &request_body)?;
        assert_eq!(raw_response.len(), 1805); // precalculated
        Ok(())
    });
}

#[test]
fn should_make_http_call_and_parse_block_number() {
    let (offchain, state) = TestOffchainExt::new();
    let mut t = sp_io::TestExternalities::default();
    t.register_extension(OffchainWorkerExt::new(offchain));

    evm_block_response(&mut state.write());

    let _: Result<(), OffchainErr<u32>> = t.execute_with(|| {
        let rpc_endpoint = get_rpc_endpoint();
        let network_data = prepare_evm_network(Some(1), None);

        let request_body = SlowClap::prepare_request_body_for_latest_block(&network_data);
        let raw_response = SlowClap::fetch_from_remote(&rpc_endpoint, &request_body)?;
        let evm_block_number = SlowClap::parse_evm_response(&raw_response).map(
            |parsed_response| match parsed_response {
                EvmResponseType::BlockNumber(block_number) => block_number,
                EvmResponseType::TransactionLogs(_) => 0,
            },
        )?;

        assert_eq!(evm_block_number, 20335745);
        Ok(())
    });
}

#[test]
fn should_make_http_call_and_parse_logs() {
    let (offchain, state) = TestOffchainExt::new();
    let (pool, _) = TestTransactionPoolExt::new();
    let mut t = sp_io::TestExternalities::default();

    t.register_extension(OffchainDbExt::new(offchain.clone()));
    t.register_extension(OffchainWorkerExt::new(offchain));
    t.register_extension(TransactionPoolExt::new(pool));

    evm_logs_response(&mut state.write());

    let _: Result<(), OffchainErr<u32>> = t.execute_with(|| {
        let rpc_endpoint = get_rpc_endpoint();

        let from_block: u64 = 20335770;
        let to_block: u64 = 20335858;

        let network_data = prepare_evm_network(None, None);
        let request_body = SlowClap::prepare_request_body_for_latest_transfers(
            from_block,
            to_block,
            &network_data,
        );

        let raw_response = SlowClap::fetch_from_remote(&rpc_endpoint, &request_body)?;
        match SlowClap::parse_evm_response(&raw_response)? {
            EvmResponseType::BlockNumber(_) => assert_eq!(1, 0), // force break
            EvmResponseType::TransactionLogs(evm_logs) => assert_eq!(evm_logs.len(), 2),
        }

        Ok(())
    });
}

#[test]
fn should_emit_black_swan_if_not_enough_authorities_left() {
    let (network_id, _, _) = generate_unique_hash(None, None, None, None, None);

    new_test_ext().execute_with(|| {
        let session_index = advance_session_and_get_index();
        prepare_evm_network(None, None);

        assert_ok!(do_clap_from(session_index, network_id, 0, false));
        assert_ok!(do_clap_from(session_index, network_id, 1, false));
        Session::disable_index(2);
        Session::disable_index(3);

        advance_session();
        advance_session();

        System::assert_has_event(RuntimeEvent::SlowClap(crate::Event::BlackSwan));
    });
}

#[test]
fn should_emit_offence_on_missed_clap() {
    let (network_id, _, _) = generate_unique_hash(None, None, None, None, None);

    new_test_ext().execute_with(|| {
        let session_index = advance_session_and_get_index();
        prepare_evm_network(None, None);

        assert_ok!(do_clap_from(session_index, network_id, 1, false));
        assert_ok!(do_clap_from(session_index, network_id, 2, false));
        assert_ok!(do_clap_from(session_index, network_id, 3, false));

        advance_session();
        advance_session();

        System::assert_has_event(RuntimeEvent::SlowClap(
            crate::Event::SomeAuthoritiesTrottling {
                throttling: vec![(0, 0)],
            },
        ));
    });
}

#[test]
fn should_emit_offence_on_fake_clap() {
    let (network_id, _, _) = generate_unique_hash(None, None, None, None, None);

    new_test_ext().execute_with(|| {
        let session_index = advance_session_and_get_index();
        prepare_evm_network(None, None);

        assert_ok!(do_clap_from(session_index, network_id, 0, false));
        assert_ok!(do_clap_from(session_index, network_id, 1, false));

        advance_session();
        advance_session();

        System::assert_has_event(RuntimeEvent::SlowClap(
            crate::Event::SomeAuthoritiesTrottling {
                throttling: vec![(0, 0), (1, 1)],
            },
        ));
    });
}

#[test]
fn should_not_emit_offence_if_already_disabled() {
    let (network_id, _, _) = generate_unique_hash(None, None, None, None, None);

    new_test_ext().execute_with(|| {
        let session_index = advance_session_and_get_index();
        prepare_evm_network(None, None);

        assert_ok!(do_clap_from(session_index, network_id, 0, false));
        assert_ok!(do_clap_from(session_index, network_id, 1, false));
        assert_eq!(Session::disable_index(0), true);

        advance_session();
        advance_session();

        System::assert_has_event(RuntimeEvent::SlowClap(
            crate::Event::SomeAuthoritiesTrottling {
                throttling: vec![(1, 1)],
            },
        ));
    });
}

#[test]
fn should_emit_authorities_equilibrium_if_no_deviation() {
    let (network_id, _, _) = generate_unique_hash(None, None, None, None, None);

    new_test_ext().execute_with(|| {
        let session_index = advance_session_and_get_index();
        prepare_evm_network(None, None);

        assert_ok!(do_clap_from(session_index, network_id, 0, false));
        assert_ok!(do_clap_from(session_index, network_id, 1, false));
        assert_ok!(do_clap_from(session_index, network_id, 2, false));
        assert_ok!(do_clap_from(session_index, network_id, 3, false));

        advance_session();
        advance_session();

        let session_index = Session::session_index();

        for index in 0..10 {
            let transaction_hash = H256::repeat_byte(index as u8);
            let receiver: u64 = index + 1;
            let amount: u64 = 1_000_000_000_000 * receiver;

            for authority_index in 0..=3 {
                let clap = Clap {
                    block_number: 1337u64,
                    removed: false,
                    transaction_hash,
                    authority_index,
                    session_index,
                    network_id,
                    receiver,
                    amount,
                };

                let unique_hash = SlowClap::generate_unique_hash(&clap);
                let authority = UintAuthorityId::from(index as u64);
                let signature = authority.sign(&clap.encode()).unwrap();

                assert_ok!(SlowClap::slow_clap(RuntimeOrigin::none(), clap, signature));
                assert_clapped(&session_index, &unique_hash, authority_index, true);
            }
        }

        advance_session();
        advance_session();

        let binding = System::events();
        let number_of_events = binding
            .iter()
            .filter(|x| {
                x.event == RuntimeEvent::SlowClap(crate::Event::AuthoritiesApplauseEquilibrium)
            })
            .count();

        assert_eq!(number_of_events, 6);
    });
}

#[test]
fn should_clear_sesions_based_on_history_depth() {
    let (network_id, _, unique_hash) = generate_unique_hash(None, None, None, None, None);

    new_test_ext().execute_with(|| {
        let history_depth = HistoryDepth::get();
        let session_index = advance_session_and_get_index();
        prepare_evm_network(None, None);

        assert_eq!(
            ApplauseDetails::<Runtime>::get(&session_index, &unique_hash),
            None
        );
        assert_ok!(do_clap_from(session_index, network_id, 0, false));
        assert_ok!(do_clap_from(session_index, network_id, 1, false));
        assert_ok!(do_clap_from(session_index, network_id, 2, false));
        assert_ok!(do_clap_from(session_index, network_id, 3, false));
        assert_eq!(
            ApplauseDetails::<Runtime>::get(&session_index, &unique_hash).is_some(),
            true
        );

        for _ in 0..history_depth {
            advance_session();
        }

        assert_eq!(
            ApplauseDetails::<Runtime>::get(&session_index, &unique_hash),
            None
        );
    });
}

#[test]
fn should_collect_commission_accordingly() {
    let (network_id, _, _) = generate_unique_hash(None, None, None, None, None);
    let (_, _, amount, _) = get_mocked_metadata();

    new_test_ext().execute_with(|| {
        let _ = prepare_evm_network(None, Some(500_000_000));
        let session_index = advance_session_and_get_index();

        assert_eq!(Networks::accumulated_commission(), 0);
        assert_ok!(do_clap_from(session_index, network_id, 0, false));
        assert_ok!(do_clap_from(session_index, network_id, 1, false));
        assert_ok!(do_clap_from(session_index, network_id, 2, false));
        assert_eq!(Networks::accumulated_commission(), amount.saturating_div(2));
    });
}

#[test]
fn should_increase_gatkeeper_amount_accordingly() {
    let (network_id, _, _) = generate_unique_hash(None, None, None, None, None);
    let (_, _, amount, _) = get_mocked_metadata();

    new_test_ext().execute_with(|| {
        let _ = prepare_evm_network(None, Some(500_000_000));
        let session_index = advance_session_and_get_index();

        assert_eq!(Networks::gatekeeper_amount(network_id), 0);
        assert_eq!(Networks::bridged_imbalance().bridged_in, 0);
        assert_eq!(Networks::bridged_imbalance().bridged_out, 0);

        assert_ok!(do_clap_from(session_index, network_id, 0, false));
        assert_ok!(do_clap_from(session_index, network_id, 1, false));
        assert_ok!(do_clap_from(session_index, network_id, 2, false));

        assert_eq!(Networks::gatekeeper_amount(network_id), amount);
        assert_eq!(
            Networks::bridged_imbalance().bridged_in,
            amount.saturating_div(2)
        );
        assert_eq!(Networks::bridged_imbalance().bridged_out, 0);
    });
}

#[test]
fn should_mark_clapped_transaction_when_clap_received() {
    let (network_id, _, unique_hash) = generate_unique_hash(None, None, None, None, None);

    new_test_ext().execute_with(|| {
        let session_index = advance_session_and_get_index();
        prepare_evm_network(None, None);

        assert_ok!(do_clap_from(session_index, network_id, 0, false));
        assert_clapped(&session_index, &unique_hash, 0, true);
        assert_ok!(do_clap_from(session_index, network_id, 1, false));
        assert_clapped(&session_index, &unique_hash, 1, true);
        assert_ok!(do_clap_from(session_index, network_id, 2, false));
        assert_clapped(&session_index, &unique_hash, 2, true);
        assert_ok!(do_clap_from(session_index, network_id, 3, false));
        assert_clapped(&session_index, &unique_hash, 3, true);
    });
}

#[test]
fn should_applause_and_take_next_claps() {
    let (network_id, _, unique_hash) = generate_unique_hash(None, None, None, None, None);
    let (_, receiver, amount, _) = get_mocked_metadata();

    new_test_ext().execute_with(|| {
        let mut clapped_amount = Default::default();
        let session_index = advance_session_and_get_index();
        prepare_evm_network(None, None);

        assert_eq!(Balances::total_balance(&receiver), 0);
        for i in 0..=3 {
            assert_ok!(do_clap_from(session_index, network_id, i, false));
            let account_id =
                TestExposureListener::get_account_by_index(i as usize).unwrap_or_default();
            clapped_amount = TestExposureListener::get_validator_exposure(&account_id)
                .saturating_add(clapped_amount);
            assert_clapped_amount(&session_index, &unique_hash, clapped_amount);
        }
        assert_applaused(&session_index, &unique_hash);
        assert_eq!(Balances::total_balance(&receiver), amount);
    });
}

#[test]
fn should_throw_error_on_clap_duplication() {
    let (network_id, _, unique_hash) = generate_unique_hash(None, None, None, None, None);

    new_test_ext().execute_with(|| {
        let session_index = advance_session_and_get_index();
        prepare_evm_network(None, None);

        assert_ok!(do_clap_from(session_index, network_id, 0, false));
        assert_clapped(&session_index, &unique_hash, 0, true);
        assert_err!(
            do_clap_from(session_index, network_id, 0, false),
            Error::<Runtime>::AlreadyClapped
        );
        assert_clapped(&session_index, &unique_hash, 0, true);
    });
}

#[test]
fn should_remove_clap_by_authority() {
    let (network_id, _, unique_hash) = generate_unique_hash(None, None, None, None, None);

    new_test_ext().execute_with(|| {
        let session_index = advance_session_and_get_index();
        prepare_evm_network(None, None);

        assert_ok!(do_clap_from(session_index, network_id, 0, false));
        assert_ok!(do_clap_from(session_index, network_id, 1, false));
        assert_ok!(do_clap_from(session_index, network_id, 1, true));

        assert_clapped(&session_index, &unique_hash, 0, true);
        assert_clapped(&session_index, &unique_hash, 1, false);
    });
}

#[test]
fn should_throw_error_if_executed_block_number_is_higher() {
    let (network_id, block_number, unique_hash) =
        generate_unique_hash(None, None, None, None, None);

    new_test_ext().execute_with(|| {
        let session_index = advance_session_and_get_index();
        prepare_evm_network(None, None);

        assert_eq!(
            ApplauseDetails::<Runtime>::get(session_index, unique_hash),
            None
        );
        assert_ok!(do_clap_from(session_index, network_id, 0, false));
        assert_ok!(do_clap_from(session_index, network_id, 1, false));
        assert_ok!(do_clap_from(session_index, network_id, 2, false));
        assert_ok!(do_clap_from(session_index, network_id, 3, false));

        assert_clapped(&session_index, &unique_hash, 0, true);
        assert_applaused(&session_index, &unique_hash);

        assert_err!(
            do_clap_from_at_block(session_index, network_id, 0, block_number - 1),
            Error::<Runtime>::ExecutedBlockIsHigher,
        );
    });
}

#[test]
fn should_throw_error_if_validator_disabled_and_ignore_later() {
    let (network_id, _, unique_hash) = generate_unique_hash(None, None, None, None, None);

    new_test_ext().execute_with(|| {
        let session_index = advance_session_and_get_index();
        prepare_evm_network(None, None);

        assert_eq!(
            ApplauseDetails::<Runtime>::get(&session_index, &unique_hash),
            None
        );
        assert_eq!(Session::disable_index(0), true);
        assert_err!(
            do_clap_from(session_index, network_id, 0, false),
            Error::<Runtime>::DisabledAuthority,
        );
        assert_eq!(
            ApplauseDetails::<Runtime>::get(&session_index, &unique_hash),
            None
        );

        advance_session();
        let session_index = session_index + 1;

        assert_eq!(Session::disable_index(0), true);
        assert_err!(
            do_clap_from(session_index, network_id, 0, false),
            Error::<Runtime>::DisabledAuthority,
        );
        assert_eq!(
            ApplauseDetails::<Runtime>::get(&session_index, &unique_hash),
            None
        );
    });
}

#[test]
fn should_clap_without_applause_on_gatekeeper_amount_overflow() {
    let big_amount: u64 = u64::MAX;
    let first_receiver: u64 = 1337;
    let second_receiver: u64 = 420;

    let (network_id, block_number, unique_hash) =
        generate_unique_hash(None, None, Some(first_receiver), Some(big_amount), None);

    new_test_ext().execute_with(|| {
        let session_index = advance_session_and_get_index();
        prepare_evm_network(None, None);

        for authority_index in 0..=3 {
            let clap = Clap {
                removed: false,
                block_number,
                transaction_hash: H256::repeat_byte(1u8),
                session_index,
                authority_index,
                network_id,
                receiver: first_receiver,
                amount: big_amount,
            };
            let authority = UintAuthorityId::from(authority_index as u64);
            let signature = authority.sign(&clap.encode()).unwrap();
            assert_ok!(SlowClap::slow_clap(RuntimeOrigin::none(), clap, signature));
            assert_clapped(&session_index, &unique_hash, authority_index, true);
        }

        assert_applaused(&session_index, &unique_hash);
        assert_eq!(Balances::total_balance(&first_receiver), big_amount);
        assert_eq!(Balances::total_balance(&second_receiver), 0);

        for authority_index in 0..=3 {
            let clap = Clap {
                block_number,
                removed: false,
                transaction_hash: H256::repeat_byte(3u8),
                session_index,
                authority_index,
                network_id,
                receiver: second_receiver,
                amount: second_receiver,
            };
            let authority = UintAuthorityId::from(authority_index as u64);
            let signature = authority.sign(&clap.encode()).unwrap();
            assert_ok!(SlowClap::slow_clap(RuntimeOrigin::none(), clap, signature));
        }

        assert_eq!(Balances::total_balance(&first_receiver), big_amount);
        assert_eq!(Balances::total_balance(&second_receiver), 0);

        assert_eq!(Networks::gatekeeper_amount(network_id), big_amount);
        assert_eq!(Networks::bridged_imbalance().bridged_in, big_amount);
        assert_eq!(Networks::bridged_imbalance().bridged_out, 0);
    });
}

#[test]
fn should_nullify_commission_on_finalize() {
    let total_staked = 69_000_000;
    let total_issuance = 100_000_000;

    let (network_id, _, unique_hash) = generate_unique_hash(None, None, None, None, None);
    let (_, _, amount, _) = get_mocked_metadata();

    new_test_ext().execute_with(|| {
        let _ = prepare_evm_network(None, Some(1_000_000_000));
        let session_index = advance_session_and_get_index();

        assert_eq!(Networks::accumulated_commission(), 0);
        assert_ok!(do_clap_from(session_index, network_id, 0, false));
        assert_ok!(do_clap_from(session_index, network_id, 1, false));
        assert_ok!(do_clap_from(session_index, network_id, 2, false));
        assert_eq!(Networks::accumulated_commission(), amount);
        assert_eq!(Networks::is_nullification_period(), false);
        assert_applaused(&session_index, &unique_hash);

        assert_eq!(
            BridgedInflationCurve::<RewardCurve, Runtime>::era_payout(
                total_staked,
                total_issuance,
                0u64
            ),
            (amount, 0u64)
        ); // precomputed values
        assert_eq!(Networks::is_nullification_period(), true);
        Networks::on_finalize(System::block_number());

        assert_eq!(Networks::is_nullification_period(), false);
        assert_eq!(Networks::accumulated_commission(), 0);
        assert_ok!(do_clap_from(session_index, network_id, 3, false));
        assert_eq!(Networks::accumulated_commission(), 0);
    });
}

#[test]
fn should_avoid_applause_during_nullification_period() {
    let zero: u64 = 0u64;
    let total_staked = 69_000_000;
    let total_issuance = 100_000_000;

    let (network_id, _, unique_hash) = generate_unique_hash(None, None, None, None, None);
    let (_, receiver, amount, _) = get_mocked_metadata();

    new_test_ext().execute_with(|| {
        let _ = prepare_evm_network(None, None);
        let session_index = advance_session_and_get_index();

        assert_eq!(Networks::is_nullification_period(), false);
        assert_eq!(
            BridgedInflationCurve::<RewardCurve, Runtime>::era_payout(
                total_staked,
                total_issuance,
                zero
            ),
            (zero, zero)
        );
        assert_eq!(Networks::is_nullification_period(), true);

        assert_ok!(do_clap_from(session_index, network_id, 0, false));
        assert_ok!(do_clap_from(session_index, network_id, 1, false));
        assert_eq!(Balances::total_balance(&receiver), 0);
        assert_clapped(&session_index, &unique_hash, 0, true);
        assert_clapped(&session_index, &unique_hash, 1, true);
        assert_clapped(&session_index, &unique_hash, 2, false);
        assert_clapped(&session_index, &unique_hash, 3, false);

        Networks::on_finalize(System::block_number());
        assert_eq!(Networks::is_nullification_period(), false);

        assert_ok!(do_clap_from(session_index, network_id, 2, false));
        assert_ok!(do_clap_from(session_index, network_id, 3, false));
        assert_eq!(Balances::total_balance(&receiver), amount);

        assert_applaused(&session_index, &unique_hash);
        assert_clapped(&session_index, &unique_hash, 0, true);
        assert_clapped(&session_index, &unique_hash, 1, true);
        assert_clapped(&session_index, &unique_hash, 2, true);
        assert_clapped(&session_index, &unique_hash, 3, true);
    });
}

#[test]
fn should_avoid_session_overlap_on_mended_session_index() {
    let (network_id, _, unique_hash) = generate_unique_hash(None, None, None, None, None);
    let (_, receiver, amount, _) = get_mocked_metadata();

    new_test_ext().execute_with(|| {
        let _ = prepare_evm_network(None, None);
        let session_index = advance_session_and_get_index();

        assert_ok!(do_clap_from(session_index, network_id, 0, false));
        assert_ok!(do_clap_from(session_index, network_id, 1, false));

        assert_clapped(&session_index, &unique_hash, 0, true);
        assert_clapped(&session_index, &unique_hash, 1, true);
        assert_clapped(&session_index, &unique_hash, 2, false);
        assert_clapped(&session_index, &unique_hash, 3, false);

        advance_session();
        let new_session_index = session_index + 1;

        assert_ok!(do_clap_from(new_session_index, network_id, 2, false));
        assert_eq!(Balances::total_balance(&receiver), amount);
        assert_eq!(
            ApplauseDetails::<Runtime>::get(new_session_index, unique_hash),
            None
        );

        assert_applaused(&session_index, &unique_hash);
        assert_clapped(&session_index, &unique_hash, 0, true);
        assert_clapped(&session_index, &unique_hash, 1, true);
        assert_clapped(&session_index, &unique_hash, 2, true);
        assert_clapped(&session_index, &unique_hash, 3, false);

        advance_session();

        let new_session_index = new_session_index + 1;

        assert_ok!(do_clap_from(new_session_index, network_id, 3, false));
        assert_clapped(&new_session_index, &unique_hash, 0, false);
        assert_clapped(&new_session_index, &unique_hash, 1, false);
        assert_clapped(&new_session_index, &unique_hash, 2, false);
        assert_clapped(&new_session_index, &unique_hash, 3, true);
    });
}

#[test]
fn should_emit_event_on_each_clap_and_on_applause() {
    let (network_id, block_number, unique_hash) =
        generate_unique_hash(None, None, None, None, None);
    let (_, receiver, amount, _) = get_mocked_metadata();

    new_test_ext().execute_with(|| {
        let commission = Perbill::from_parts(100_000_000); // 10%
        let commission_amount = commission.mul_ceil(amount);
        let amount_after_commission = amount.saturating_sub(commission_amount);
        let _ = prepare_evm_network(None, Some(100_000_000));
        let session_index = advance_session_and_get_index();

        assert_eq!(
            ApplauseDetails::<Runtime>::get(&session_index, &unique_hash),
            None
        );
        for index in 0..2 {
            assert_ok!(do_clap_from(session_index, network_id, index, false));
            System::assert_last_event(RuntimeEvent::SlowClap(crate::Event::Clapped {
                clap_unique_hash: unique_hash,
                receiver: receiver.clone(),
                authority_id: index,
                removed: false,
                network_id,
                amount,
            }));
        }

        assert_ok!(do_clap_from(session_index, network_id, 2, false));
        let binding = System::events();
        let last_five_events = binding.iter().rev().take(5).collect::<Vec<_>>();

        assert_eq!(
            last_five_events[0].event,
            RuntimeEvent::SlowClap(crate::Event::Applaused {
                received_amount: amount_after_commission,
                receiver: receiver.clone(),
                block_number,
                network_id,
            })
        );
        assert_eq!(
            last_five_events[4].event,
            RuntimeEvent::SlowClap(crate::Event::Clapped {
                receiver: receiver.clone(),
                clap_unique_hash: unique_hash,
                authority_id: 2,
                removed: false,
                network_id,
                amount,
            })
        );

        assert_applaused(&session_index, &unique_hash);
        assert_ok!(do_clap_from(session_index, network_id, 3, false));
        System::assert_last_event(RuntimeEvent::SlowClap(crate::Event::Clapped {
            receiver: receiver.clone(),
            clap_unique_hash: unique_hash,
            authority_id: 3,
            removed: false,
            network_id,
            amount,
        }));
    });
}

#[test]
fn should_not_fail_on_sub_existential_balance() {
    let (network_id, _, unique_hash) = generate_unique_hash(None, None, None, None, None);
    let (_, receiver, amount, _) = get_mocked_metadata();

    new_test_ext().execute_with(|| {
        let _ = prepare_evm_network(None, Some(1_000_000_000)); // 100%
        let session_index = advance_session_and_get_index();

        assert_eq!(Networks::accumulated_commission(), 0);
        assert_eq!(Networks::gatekeeper_amount(network_id), 0);
        assert_eq!(Networks::bridged_imbalance().bridged_in, 0);
        assert_eq!(Networks::bridged_imbalance().bridged_out, 0);
        assert_eq!(Balances::total_balance(&receiver), 0);
        assert_eq!(
            ApplauseDetails::<Runtime>::get(session_index, unique_hash),
            None
        );

        assert_ok!(do_clap_from(session_index, network_id, 0, false));
        assert_ok!(do_clap_from(session_index, network_id, 1, false));
        assert_ok!(do_clap_from(session_index, network_id, 2, false));

        assert_eq!(Networks::accumulated_commission(), amount);
        assert_eq!(Networks::gatekeeper_amount(network_id), amount);
        assert_eq!(Networks::bridged_imbalance().bridged_in, 0);
        assert_eq!(Networks::bridged_imbalance().bridged_out, 0);
        assert_eq!(Balances::total_balance(&receiver), 0);
        assert_applaused(&session_index, &unique_hash);
    });
}

#[test]
fn should_register_block_commitments() {
    let (network_id, _, _) = generate_unique_hash(None, None, None, None, None);

    new_test_ext().execute_with(|| {
        let session_index = advance_session_and_get_index();
        prepare_evm_network(None, None);

        for commitment_details in BlockCommitments::<Runtime>::get(network_id).values() {
            assert_eq!(commitment_details.last_stored_block, 0);
            assert_eq!(commitment_details.last_updated, 0);
            assert_eq!(commitment_details.commits, 0);
        }

        let mut block_commitment = CommitmentDetails {
            last_stored_block: 69,
            commits: 420,
            last_updated: 1337,
        };
        for i in 0..=3 {
            assert_ok!(do_block_commitment(
                session_index,
                network_id,
                i,
                &block_commitment
            ));
        }

        block_commitment.last_updated = 1000;
        for i in 0..=3 {
            assert_err!(
                do_block_commitment(session_index, network_id, i, &block_commitment),
                Error::<Runtime>::TimeWentBackwards,
            );
        }

        for commitment_details in BlockCommitments::<Runtime>::get(network_id).values() {
            assert_eq!(commitment_details.last_stored_block, 69);
            assert_eq!(commitment_details.last_updated, 1337);
            assert_eq!(commitment_details.commits, 1);
        }

        advance_session();

        for commitment_details in BlockCommitments::<Runtime>::get(network_id).values() {
            assert_eq!(commitment_details.last_stored_block, 0);
            assert_eq!(commitment_details.last_updated, 0);
            assert_eq!(commitment_details.commits, 0);
        }
    });
}

#[test]
fn should_disable_on_commitment_inactivity() {
    let (network_id, _, _) = generate_unique_hash(None, None, None, None, None);

    new_test_ext().execute_with(|| {
        let session_index = advance_session_and_get_index();
        prepare_evm_network(None, None);

        for commitment_details in BlockCommitments::<Runtime>::get(network_id).values() {
            assert_eq!(commitment_details.last_stored_block, 0);
            assert_eq!(commitment_details.last_updated, 0);
            assert_eq!(commitment_details.commits, 0);
        }

        let timestamp = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("Time went backwards")
            .as_millis() as u64;

        let mut block_commitment = CommitmentDetails {
            last_stored_block: 69,
            commits: 420,
            last_updated: timestamp,
        };

        // two block commitments
        for extra_time in 1..=3 {
            block_commitment.last_updated += COMMITMENT_DELAY_MILLIS + extra_time;
            for i in 0..3 {
                assert_ok!(do_block_commitment(
                    session_index,
                    network_id,
                    i,
                    &block_commitment
                ));
            }
        }

        System::assert_has_event(RuntimeEvent::SlowClap(
            crate::Event::SomeAuthoritiesDelayed {
                delayed: vec![(3, 3)],
            },
        ));

        for commitment_details in BlockCommitments::<Runtime>::get(network_id)
            .values()
            .take(2)
        {
            assert_eq!(commitment_details.commits, 3);
        }
    });
}

#[test]
fn should_disable_on_commitment_block_deviation() {
    let (network_id, _, _) = generate_unique_hash(None, None, None, None, None);

    new_test_ext().execute_with(|| {
        let session_index = advance_session_and_get_index();
        prepare_evm_network(None, None);

        for commitment_details in BlockCommitments::<Runtime>::get(network_id).values() {
            assert_eq!(commitment_details.last_stored_block, 0);
            assert_eq!(commitment_details.last_updated, 0);
        }

        let timestamp = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("Time went backwards")
            .as_millis() as u64;

        let mut block_commitment = CommitmentDetails {
            last_stored_block: 9_500_000,
            commits: 420,
            last_updated: timestamp,
        };

        let mut bad_block_commitment = CommitmentDetails {
            last_stored_block: 9_100_000,
            commits: 420,
            last_updated: timestamp,
        };

        // two block commitments
        for extra_time in 1..=3 {
            block_commitment.last_updated += COMMITMENT_DELAY_MILLIS + extra_time;
            bad_block_commitment.last_updated += COMMITMENT_DELAY_MILLIS + extra_time;
            for i in 0..3 {
                assert_ok!(do_block_commitment(
                    session_index,
                    network_id,
                    i,
                    &block_commitment
                ));
            }
            assert_ok!(do_block_commitment(
                session_index,
                network_id,
                3,
                &bad_block_commitment
            ));
        }

        System::assert_has_event(RuntimeEvent::SlowClap(
            crate::Event::SomeAuthoritiesDelayed {
                delayed: vec![(3, 3)],
            },
        ));
    });
}

#[test]
fn should_throw_error_on_fast_commitments() {
    let (network_id, _, _) = generate_unique_hash(None, None, None, None, None);

    new_test_ext().execute_with(|| {
        let session_index = advance_session_and_get_index();
        prepare_evm_network(None, None);

        let timestamp = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("Time went backwards")
            .as_millis() as u64;

        let mut block_commitment = CommitmentDetails {
            last_stored_block: 9_500_000,
            commits: 420,
            last_updated: timestamp,
        };

        assert_ok!(do_block_commitment(
            session_index,
            network_id,
            0,
            &block_commitment
        ));
        assert_err!(
            do_block_commitment(session_index, network_id, 0, &block_commitment),
            Error::<Runtime>::TimeWentBackwards,
        );

        block_commitment.last_updated += COMMITMENT_DELAY_MILLIS / 2;
        assert_err!(
            do_block_commitment(session_index, network_id, 0, &block_commitment),
            Error::<Runtime>::TimeWentBackwards,
        );

        block_commitment.last_updated += COMMITMENT_DELAY_MILLIS / 2;
        assert_err!(
            do_block_commitment(session_index, network_id, 0, &block_commitment),
            Error::<Runtime>::TimeWentBackwards,
        );

        block_commitment.last_updated += 1;
        assert_ok!(do_block_commitment(
            session_index,
            network_id,
            0,
            &block_commitment
        ));

        block_commitment.last_updated = timestamp;
        assert_err!(
            do_block_commitment(session_index, network_id, 0, &block_commitment),
            Error::<Runtime>::TimeWentBackwards,
        );

        for commitment_details in BlockCommitments::<Runtime>::get(network_id).values() {
            assert_eq!(commitment_details.last_stored_block, 9_500_000);
            assert_eq!(
                commitment_details.last_updated,
                timestamp + COMMITMENT_DELAY_MILLIS + 1
            );
            assert_eq!(commitment_details.commits, 2);
        }
    });
}

#[test]
fn should_not_offend_disabled_authorities() {
    let (network_id, _, _) = generate_unique_hash(None, None, None, None, None);

    new_test_ext().execute_with(|| {
        let session_index = advance_session_and_get_index();
        prepare_evm_network(None, None);

        assert_ok!(do_clap_from(session_index, network_id, 0, false));
        assert_ok!(do_clap_from(session_index, network_id, 1, false));
        assert_ok!(do_clap_from(session_index, network_id, 2, false));

        let timestamp = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("Time went backwards")
            .as_millis() as u64;

        let mut block_commitment = CommitmentDetails {
            last_stored_block: 9_500_000,
            commits: 420,
            last_updated: timestamp,
        };

        Session::disable_index(3);

        // two block commitments
        for extra_time in 1..=3 {
            block_commitment.last_updated += COMMITMENT_DELAY_MILLIS + extra_time;
            for i in 0..3 {
                assert_ok!(do_block_commitment(
                    session_index,
                    network_id,
                    i,
                    &block_commitment
                ));
            }
        }

        System::assert_has_event(RuntimeEvent::SlowClap(
            crate::Event::AuthoritiesCommitmentEquilibrium,
        ));
    });
}

#[test]
fn should_not_slash_by_applause_if_disabled_by_commitment() {
    let (network_id, _, _) = generate_unique_hash(None, None, None, None, None);

    new_test_ext().execute_with(|| {
        let session_index = advance_session_and_get_index();
        prepare_evm_network(None, None);

        for commitment_details in BlockCommitments::<Runtime>::get(network_id).values() {
            assert_eq!(commitment_details.last_stored_block, 0);
            assert_eq!(commitment_details.last_updated, 0);
        }

        let timestamp = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("Time went backwards")
            .as_millis() as u64;

        let mut block_commitment = CommitmentDetails {
            last_stored_block: 9_500_000,
            commits: 420,
            last_updated: timestamp,
        };

        Session::disable_index(3);

        // two block commitments
        for extra_time in 1..=3 {
            block_commitment.last_updated += COMMITMENT_DELAY_MILLIS + extra_time;
            for i in 0..3 {
                assert_ok!(do_block_commitment(
                    session_index,
                    network_id,
                    i,
                    &block_commitment
                ));
            }
        }

        System::assert_has_event(RuntimeEvent::SlowClap(
            crate::Event::AuthoritiesCommitmentEquilibrium,
        ));
    });
}

#[test]
fn should_not_nullify_on_incorrect_block_commitment() {
    let (network_id, _, _) = generate_unique_hash(None, None, None, None, None);

    new_test_ext().execute_with(|| {
        let session_index = advance_session_and_get_index();
        prepare_evm_network(None, None);

        let timestamp = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("Time went backwards")
            .as_millis() as u64;

        let mut block_commitment = CommitmentDetails {
            last_stored_block: 9_500_000,
            commits: 420,
            last_updated: timestamp,
        };

        for i in 0..4 {
            assert_ok!(do_block_commitment(
                session_index,
                network_id,
                i,
                &block_commitment
            ));
        }

        block_commitment.last_updated = 0;
        assert_err!(
            do_block_commitment(session_index, network_id, 3, &block_commitment),
            Error::<Runtime>::TimeWentBackwards
        );

        for commitment_details in BlockCommitments::<Runtime>::get(network_id).values() {
            assert_eq!(commitment_details.last_stored_block, 9_500_000);
            assert_eq!(commitment_details.last_updated, timestamp);
            assert_eq!(commitment_details.commits, 1);
        }
    });
}

#[test]
fn should_split_commit_slash_between_active_validators() {
    let (network_id, _, _) = generate_unique_hash(None, None, None, None, None);

    new_test_ext().execute_with(|| {
        let session_index = advance_session_and_get_index();
        prepare_evm_network(None, None);

        let timestamp = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("Time went backwards")
            .as_millis() as u64;

        let mut block_commitment = CommitmentDetails {
            last_stored_block: 9_500_000,
            commits: 420,
            last_updated: timestamp,
        };

        for extra_time in 1..=3 {
            if extra_time < 3 {
                let offences = Offences::get();
                assert_eq!(offences.len(), 0);
            }

            block_commitment.last_updated += COMMITMENT_DELAY_MILLIS + extra_time;
            for i in 0..3 {
                assert_ok!(do_block_commitment(
                    session_index,
                    network_id,
                    i,
                    &block_commitment
                ));
            }
        }

        System::assert_has_event(RuntimeEvent::SlowClap(
            crate::Event::SomeAuthoritiesDelayed {
                delayed: vec![(3, 3)],
            },
        ));

        let offences = Offences::get();
        assert_eq!(offences.len(), 3);
        for offence in offences {
            assert_eq!(offence.0, vec![0, 1, 2]);
            assert_eq!(offence.1.session_index, session_index);
            assert_eq!(offence.1.validator_set_count, 4);
            assert_eq!(offence.1.offenders, vec![(3, 3)]);
            assert_eq!(offence.1.validator_set_count, 4);
            assert_eq!(offence.1.offence_type, OffenceType::CommitmentOffence);
        }
    });
}

fn assert_clapped_amount(
    session_index: &SessionIndex,
    unique_hash: &H256,
    clapped_amount: BalanceOf<Runtime>,
) {
    let maybe_details = ApplauseDetails::<Runtime>::get(session_index, unique_hash);
    assert!(maybe_details.is_some());
    let actual_clapped_amount = maybe_details
        .map(|details| details.clapped_amount)
        .unwrap_or_default();
    assert_eq!(actual_clapped_amount, clapped_amount);
}

fn assert_applaused(session_index: &SessionIndex, unique_hash: &H256) {
    let maybe_details = ApplauseDetails::<Runtime>::get(session_index, unique_hash);
    assert!(maybe_details.is_some());
    let is_applaused = maybe_details
        .map(|details| details.finalized)
        .unwrap_or_default();
    assert!(is_applaused);
}

fn prepare_evm_network(
    maybe_network_id: Option<u32>,
    maybe_incoming_commission: Option<u32>,
) -> NetworkData {
    let network_data = NetworkData {
        chain_name: "Ethereum".into(),
        default_endpoints: get_rpc_endpoints(),
        finality_delay: 69,
        avg_block_speed: 69,
        rate_limit_delay: 69,
        block_distance: 69,
        network_type: ghost_networks::NetworkType::Evm,
        gatekeeper: get_gatekeeper(),
        topic_name: get_topic_name(),
        incoming_fee: maybe_incoming_commission.unwrap_or_default(),
        outgoing_fee: 0,
    };

    assert_ok!(Networks::register_network(
        RuntimeOrigin::root(),
        maybe_network_id.unwrap_or(1u32),
        network_data.clone()
    ));

    network_data
}

fn do_block_commitment(
    session_index: u32,
    network_id: u32,
    authority_index: u32,
    commitment: &CommitmentDetails,
) -> dispatch::DispatchResult {
    let block_commitment = BlockCommitment {
        session_index,
        authority_index,
        network_id,
        commitment: *commitment,
    };
    let authority = UintAuthorityId::from(authority_index as u64);
    let signature = authority.sign(&block_commitment.encode()).unwrap();

    SlowClap::pre_dispatch(&crate::Call::commit_block {
        block_commitment: block_commitment.clone(),
        signature: signature.clone(),
    })
    .map_err(|e| <&'static str>::from(e))?;

    SlowClap::commit_block(RuntimeOrigin::none(), block_commitment, signature)
}

fn do_clap_from(
    session_index: u32,
    network_id: u32,
    authority_index: u32,
    transaction_removed: bool,
) -> dispatch::DispatchResult {
    let (transaction_hash, receiver, amount, block_number) = get_mocked_metadata();
    let clap = Clap {
        block_number,
        removed: transaction_removed,
        transaction_hash,
        session_index,
        authority_index,
        network_id,
        receiver,
        amount,
    };
    let authority = UintAuthorityId::from(authority_index as u64);
    let signature = authority.sign(&clap.encode()).unwrap();

    SlowClap::pre_dispatch(&crate::Call::slow_clap {
        clap: clap.clone(),
        signature: signature.clone(),
    })
    .map_err(|e| <&'static str>::from(e))?;

    SlowClap::slow_clap(RuntimeOrigin::none(), clap, signature)
}

fn do_clap_from_at_block(
    session_index: u32,
    network_id: u32,
    authority_index: u32,
    block_number: u64,
) -> dispatch::DispatchResult {
    let transaction_hash = H256::repeat_byte(96u8);
    let receiver: u64 = 1337;
    let amount: u64 = 420_000_000_000_000;

    let clap = Clap {
        block_number,
        removed: false,
        transaction_hash,
        session_index,
        authority_index,
        network_id,
        receiver,
        amount,
    };
    let authority = UintAuthorityId::from(authority_index as u64);
    let signature = authority.sign(&clap.encode()).unwrap();

    SlowClap::pre_dispatch(&crate::Call::slow_clap {
        clap: clap.clone(),
        signature: signature.clone(),
    })
    .map_err(|e| <&'static str>::from(e))?;

    SlowClap::slow_clap(RuntimeOrigin::none(), clap, signature)
}

fn assert_clapped(
    session_index: &SessionIndex,
    unique_hash: &H256,
    authority_index: u32,
    is_clapped: bool,
) {
    let maybe_details = ApplauseDetails::<Runtime>::get(session_index, unique_hash);
    assert!(maybe_details.is_some());
    let bitmap = maybe_details.map(|details| details.authorities).unwrap();
    assert_eq!(bitmap.exists(&authority_index), is_clapped);
}

fn advance_session_and_get_index() -> u32 {
    advance_session();
    assert_eq!(Session::validators(), Vec::<u64>::new());

    advance_session();
    Session::session_index()
}

fn generate_unique_hash(
    maybe_network_id: Option<u32>,
    maybe_transaction_hash: Option<H256>,
    maybe_receiver: Option<u64>,
    maybe_amount: Option<u64>,
    maybe_block_number: Option<u64>,
) -> (NetworkIdOf<Runtime>, u64, H256) {
    let network_id = maybe_network_id.unwrap_or(1);

    let (transaction_hash, receiver, amount, block_number) = get_mocked_metadata();

    let transaction_hash = maybe_transaction_hash.unwrap_or(transaction_hash);
    let receiver = maybe_receiver.unwrap_or(receiver);
    let amount = maybe_amount.unwrap_or(amount);
    let block_number = maybe_block_number.unwrap_or(block_number);

    let clap = Clap {
        session_index: 0,
        authority_index: 0,
        removed: false,
        block_number,
        transaction_hash,
        network_id,
        receiver,
        amount,
    };

    let unique_hash = SlowClap::generate_unique_hash(&clap);

    (network_id, block_number, unique_hash)
}

fn get_rpc_endpoint() -> Vec<u8> {
    b"https://rpc.endpoint.network.com".to_vec()
}

fn get_rpc_endpoints() -> Vec<Vec<u8>> {
    vec![
        get_rpc_endpoint(),
        b"https://other.endpoint.network.com".to_vec(),
    ]
}

fn get_gatekeeper() -> Vec<u8> {
    b"0x4d224452801ACEd8B2F0aebE155379bb5D594381".to_vec()
}

fn get_topic_name() -> Vec<u8> {
    b"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef".to_vec()
}

fn get_mocked_metadata() -> (H256, u64, u64, u64) {
    let transaction_hash = H256::repeat_byte(69u8);
    let receiver: u64 = 1337;
    let amount: u64 = 420_000_000_000_000;
    let block_number: u64 = 555;

    (transaction_hash, receiver, amount, block_number)
}

fn evm_block_response(state: &mut testing::OffchainState) {
    let expected_body = br#"{"id":0,"jsonrpc":"2.0","method":"eth_blockNumber"}"#.to_vec();
    state.expect_request(testing::PendingRequest {
        method: "POST".into(),
        uri: "https://rpc.endpoint.network.com".into(),
        headers: vec![
            ("Accept".to_string(), "application/json".to_string()),
            ("Content-Type".to_string(), "application/json".to_string()),
        ],
        response: Some(b"{\"id\":0,\"jsonrpc\":\"2.0\",\"result\":\"0x1364c81\"}".to_vec()),
        body: expected_body,
        sent: true,
        ..Default::default()
    });
}

fn evm_logs_response(state: &mut testing::OffchainState) {
    let expected_body = br#"{"id":0,"jsonrpc":"2.0","method":"eth_getLogs","params":[{"fromBlock":"0x1364c9a","toBlock":"0x1364cf2","address":"0x4d224452801ACEd8B2F0aebE155379bb5D594381","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"]}]}"#.to_vec();

    let expected_response = br#"{
        "id":0,
        "jsonrpc":"2.0",
        "result":[
            {
                "address":"0x4d224452801aced8b2f0aebe155379bb5d594381",
                "topics":[
                    "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
                    "0x000000000000000000000000d91efec7e42f80156d1d9f660a69847188950747",
                    "0x0000000000000000000000005e611dfbe71b988cf2a3e5fe4191b5e3d4c4212a"
                ],
                "data":"0x000000000000000000000000000000000000000000000046f0d522a71fdac000",
                "blockNumber":"0x1364c9d",
                "transactionHash":"0xa82f2fe87f4ba01ab3bd2cd4d0fe75a26f0e9a37e2badc004a0e38f9d088a758",
                "transactionIndex":"0x11",
                "blockHash":"0x4b08f82d5a948c0bc5c23c8ddce55e5b4536d7454be53668bbd985ef13dca04a",
                "logIndex":"0x92",
                "removed":false
            },
            {
                "address":"0x4d224452801aced8b2f0aebe155379bb5d594381",
                "topics":[
                    "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
                    "0x0000000000000000000000005954ab967bc958940b7eb73ee84797dc8a2afbb9",
                    "0x000000000000000000000000465165be7cacdbd2cbc8334f549fab9783ad6e7a"
                ],
                "data":"0x0000000000000000000000000000000000000000000000027c790defec959e75",
                "blockNumber":"0x1364cf1",
                "transactionHash":"0xd9f02b79a90db7536b0afb5e24bbc1f4319dc3d8c57af7c39941acd249ec053a",
                "transactionIndex":"0x2c",
                "blockHash":"0x6876b18f5081b5d24d5a73107a667a7713256f6e51edbbe52bf8df24e340b0f7",
                "logIndex":"0x14b",
                "removed":false
            }
        ]
    }"#.to_vec();

    state.expect_request(testing::PendingRequest {
        method: "POST".into(),
        uri: "https://rpc.endpoint.network.com".into(),
        headers: vec![
            ("Accept".to_string(), "application/json".to_string()),
            ("Content-Type".to_string(), "application/json".to_string()),
        ],
        response_headers: vec![
            ("Accept".to_string(), "application/json".to_string()),
            ("Content-Type".to_string(), "application/json".to_string()),
        ],
        body: expected_body,
        response: Some(expected_response),
        sent: true,
        ..Default::default()
    });
}
