#![cfg(test)]

use std::collections::HashMap;

use super::*;
use crate::mock::*;
use crate::{evm_types::Log, migrations::v3::OldCommitmentDetails};

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 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_evm_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_evm_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_endpoints = get_rpc_endpoints();
        let network_data = prepare_evm_network(Some(1), None);
        let request_body = SlowClap::prepare_evm_request_body_for_latest_block(&network_data);

        let pending_requests =
            SlowClap::prepare_pending_evm_requests(&rpc_endpoints, &request_body)?;
        let raw_responses = SlowClap::fetch_multiple_evm_from_remote(pending_requests);
        let raw_response = raw_responses.first().unwrap();

        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_endpoints = get_rpc_endpoints();

        let network_data = prepare_evm_network(Some(1), None);
        let request_body = SlowClap::prepare_evm_request_body_for_latest_transfers(
            from_block,
            to_block,
            &network_data,
        );

        let pending_requests =
            SlowClap::prepare_pending_evm_requests(&rpc_endpoints, &request_body)?;
        let raw_responses = SlowClap::fetch_multiple_evm_from_remote(pending_requests);
        let raw_response = raw_responses.first().unwrap();

        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_endpoints = get_rpc_endpoints();
        let network_data = prepare_evm_network(Some(1), None);

        let request_body = SlowClap::prepare_evm_request_body_for_latest_block(&network_data);
        let pending_requests =
            SlowClap::prepare_pending_evm_requests(&rpc_endpoints, &request_body)?;
        let raw_responses = SlowClap::fetch_multiple_evm_from_remote(pending_requests);
        let raw_response = raw_responses.first().unwrap();

        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_endpoints = get_rpc_endpoints();

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

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

        let pending_requests =
            SlowClap::prepare_pending_evm_requests(&rpc_endpoints, &request_body)?;
        let raw_responses = SlowClap::fetch_multiple_evm_from_remote(pending_requests);
        let raw_response = raw_responses.first().unwrap();

        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_prepare_correct_evm_request_for_latest_block() {
    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_endpoints = get_rpc_endpoints();
        let network_data = prepare_evm_network(Some(1), None);
        let request_body = SlowClap::prepare_evm_request_body_for_latest_block(&network_data);

        let pending_requests =
            SlowClap::prepare_pending_evm_requests(&rpc_endpoints, &request_body)?;

        assert_eq!(pending_requests.len(), rpc_endpoints.len());
        for (i, pending_metadata) in pending_requests.iter().enumerate() {
            assert_eq!(pending_metadata.id.0, i as u16);
        }
        Ok(())
    });
}

#[test]
fn should_prepare_correct_evm_request_for_latest_transfers() {
    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_endpoints = get_rpc_endpoints();

        let network_data = prepare_evm_network(Some(1), None);
        let request_body = SlowClap::prepare_evm_request_body_for_latest_transfers(
            from_block,
            to_block,
            &network_data,
        );

        let pending_requests =
            SlowClap::prepare_pending_evm_requests(&rpc_endpoints, &request_body)?;

        assert_eq!(pending_requests.len(), rpc_endpoints.len());
        for (i, pending_metadata) in pending_requests.iter().enumerate() {
            assert_eq!(pending_metadata.id.0, i as u16);
        }
        Ok(())
    });
}

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

    let _: Result<(), OffchainErr<u32>> = t.execute_with(|| {
        let rpc_endpoints = vec![];
        let network_data = prepare_evm_network(Some(1), None);
        let request_body = SlowClap::prepare_evm_request_body_for_latest_block(&network_data);

        assert_err!(
            SlowClap::prepare_pending_evm_requests(&rpc_endpoints, &request_body),
            OffchainErr::NoRequestsSent,
        );

        Ok(())
    });
}

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

    let _: Result<(), OffchainErr<u32>> = t.execute_with(|| {
        let rpc_endpoints = vec![get_rpc_endpoint(), vec![0xFF]];
        let network_data = prepare_evm_network(Some(1), None);
        let request_body = SlowClap::prepare_evm_request_body_for_latest_block(&network_data);

        let pending_empty_requests =
            SlowClap::prepare_pending_evm_requests(&rpc_endpoints, &request_body)?;

        assert_eq!(pending_empty_requests.len(), 1);

        Ok(())
    });
}

#[test]
fn should_fetch_blocks_correctly() {
    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_endpoints = get_rpc_endpoints();
        let network_data = prepare_evm_network(Some(1), None);
        let request_body = SlowClap::prepare_evm_request_body_for_latest_block(&network_data);

        let pending_requests =
            SlowClap::prepare_pending_evm_requests(&rpc_endpoints, &request_body)?;
        let fetched_blocks_raw = SlowClap::fetch_multiple_evm_from_remote(pending_requests);

        assert_eq!(fetched_blocks_raw.len(), 2);

        Ok(())
    });
}

#[test]
fn should_parse_blocks_correctly() {
    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_endpoints = get_rpc_endpoints();
        let network_data = prepare_evm_network(Some(1), None);
        let request_body = SlowClap::prepare_evm_request_body_for_latest_block(&network_data);

        let pending_requests_metadata =
            SlowClap::prepare_pending_evm_requests(&rpc_endpoints, &request_body)?;
        let parsed_evm_responses =
            SlowClap::fetch_multiple_evm_from_remote(pending_requests_metadata)
                .iter()
                .filter_map(|response_bytes| {
                    let parsed_evm_response = SlowClap::parse_evm_response(response_bytes).ok()?;
                    Some(parsed_evm_response)
                })
                .collect::<Vec<EvmResponseType>>();

        assert_eq!(parsed_evm_responses.len(), 2);
        assert_eq!(
            parsed_evm_responses[0],
            EvmResponseType::BlockNumber(20335745)
        );
        assert_eq!(
            parsed_evm_responses[1],
            EvmResponseType::BlockNumber(20335745)
        );

        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, _) =
        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 unique_hash = SlowClap::generate_unique_hash(&clap);
            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);
            if authority_index > 2 {
                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_applaused(&session_index, &unique_hash);

        assert_eq!(
            BridgedInflationCurve::<RewardCurve, Runtime>::era_payout(
                total_staked,
                total_issuance,
                0u64
            ),
            (amount, 0u64)
        ); // precomputed values

        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_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);

        assert_eq!(BlockCommitments::<Runtime>::get(network_id).len(), 0);

        let last_stored_block = 69;
        let current_block = SlowClap::current_block_number();

        for i in 0..=3 {
            assert_ok!(do_block_commitment(
                session_index,
                network_id,
                i,
                last_stored_block,
            ));
        }

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

        let block_commitments = BlockCommitments::<Runtime>::get(network_id);
        assert_eq!(block_commitments.len(), 4);
        block_commitments.values().for_each(|details| {
            assert_eq!(details.last_stored_block, last_stored_block);
            assert_eq!(details.last_updated, current_block);
            assert_eq!(details.commits, 1);
        });

        advance_session();

        assert_eq!(BlockCommitments::<Runtime>::get(network_id).len(), 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);

        assert_eq!(BlockCommitments::<Runtime>::get(network_id).len(), 0);
        System::set_block_number(5 * EpochDuration::get() / 2);
        let last_stored_block = 69;

        for i in 0..3 {
            assert_ok!(do_block_commitment(
                session_index,
                network_id,
                i,
                last_stored_block,
            ));
        }

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

        let block_commitments = BlockCommitments::<Runtime>::get(network_id);
        assert_eq!(block_commitments.get(&3), None);
        block_commitments.values().for_each(|details| {
            assert_eq!(details.commits, 1);
            assert_eq!(details.last_stored_block, last_stored_block);
            assert_eq!(details.last_updated, current_block);
        })
    });
}

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

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

        assert_eq!(LatestExecutedBlock::<Runtime>::get(network_id), 0);
        assert_eq!(BlockCommitments::<Runtime>::get(network_id).len(), 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_ok!(do_clap_from(session_index, network_id, 3, false));

        let latest_executed_block = LatestExecutedBlock::<Runtime>::get(&network_id);
        assert_eq!(latest_executed_block, block_number);

        for low_block in 0..=block_number {
            assert_err!(
                do_block_commitment(session_index, network_id, 0, low_block),
                Error::<Runtime>::InnerTimeWentBackwards,
            );
        }

        let next_block_number = block_number.saturating_add(1);
        assert_ok!(do_block_commitment(
            session_index,
            network_id,
            0,
            next_block_number,
        ));
    });
}

#[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);

        assert_eq!(BlockCommitments::<Runtime>::get(network_id).len(), 0);

        let good_last_stored_block = 9_500_000;
        let bad_last_stored_block = 9_100_000;

        let current_block = SlowClap::current_block_number();
        for i in 0..3 {
            assert_ok!(do_block_commitment(
                session_index,
                network_id,
                i,
                good_last_stored_block,
            ));
        }
        assert_ok!(do_block_commitment(
            session_index,
            network_id,
            3,
            bad_last_stored_block,
        ));

        let to_initialize = EpochDuration::get()
            .saturating_div(BLOCK_CHECK_CYCLES)
            .saturating_mul(2);

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

        BlockCommitments::<Runtime>::get(network_id)
            .iter()
            .for_each(|(account_id, details)| {
                let block_to_be_stored = if *account_id == 3 {
                    bad_last_stored_block
                } else {
                    good_last_stored_block
                };

                assert_eq!(details.commits, 1);
                assert_eq!(details.last_stored_block, block_to_be_stored);
                assert_eq!(details.last_updated, current_block);
            })
    });
}

#[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);

        assert_eq!(BlockCommitments::<Runtime>::get(network_id).len(), 0);

        let last_stored_block = 9_500_000;
        let next_stored_block = 9_600_000;
        let current_block = SlowClap::current_block_number();

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

        BlockCommitments::<Runtime>::get(network_id)
            .get(&0)
            .map(|details| {
                assert_eq!(details.last_stored_block, last_stored_block);
                assert_eq!(details.last_updated, current_block);
                assert_eq!(details.commits, 1);
            })
            .expect("authority_index 0 has voted; qed");

        System::set_block_number(current_block + BLOCK_COMMITMENT_DELAY);

        let new_current_block = SlowClap::current_block_number();
        assert_ok!(do_block_commitment(
            session_index,
            network_id,
            0,
            next_stored_block,
        ));

        BlockCommitments::<Runtime>::get(network_id)
            .get(&0)
            .map(|details| {
                assert_eq!(details.last_stored_block, next_stored_block);
                assert_eq!(details.last_updated, new_current_block);
                assert_eq!(details.commits, 2);
            })
            .expect("authority_index 0 has voted; qed");
    });
}

#[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);

        assert_eq!(BlockCommitments::<Runtime>::get(network_id).len(), 0);
        System::set_block_number(5 * EpochDuration::get() / 2);

        let last_stored_block = 9_500_000;
        let current_block = SlowClap::current_block_number();
        let mut disabled_bitmp = BitMap::new(4);

        assert_eq!(
            DisabledAuthorityIndexes::<Runtime>::get(&session_index),
            Some(disabled_bitmp.clone())
        );
        Session::disable_index(3);
        disabled_bitmp.set(3);
        assert_eq!(
            DisabledAuthorityIndexes::<Runtime>::get(&session_index),
            Some(disabled_bitmp.clone())
        );

        for i in 0..=3 {
            assert_ok!(do_block_commitment(
                session_index,
                network_id,
                i,
                last_stored_block,
            ));
        }

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

        let block_commitments = BlockCommitments::<Runtime>::get(network_id);
        assert_eq!(
            DisabledAuthorityIndexes::<Runtime>::get(&session_index),
            Some(disabled_bitmp)
        );
        assert_eq!(block_commitments.len(), 4);
        block_commitments.values().for_each(|details| {
            assert_eq!(details.last_stored_block, last_stored_block);
            assert_eq!(details.last_updated, current_block);
            assert_eq!(details.commits, 1);
        });
    });
}

#[test]
fn should_not_register_block_commitment_on_old_blocks() {
    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_eq!(BlockCommitments::<Runtime>::get(network_id).len(), 0);

        let very_old_block = 9_499_999;
        let last_stored_block = 9_500_000;
        let current_block = SlowClap::current_block_number();

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

        assert_err!(
            do_block_commitment(session_index, network_id, 3, last_stored_block),
            Error::<Runtime>::TimeWentBackwards
        );
        assert_err!(
            do_block_commitment(session_index, network_id, 3, very_old_block),
            Error::<Runtime>::TimeWentBackwards
        );

        BlockCommitments::<Runtime>::get(network_id)
            .values()
            .for_each(|details| {
                assert_eq!(details.commits, 1);
                assert_eq!(details.last_stored_block, last_stored_block);
                assert_eq!(details.last_updated, current_block);
            })
    });
}

#[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);

        assert_eq!(BlockCommitments::<Runtime>::get(network_id).len(), 0);
        System::set_block_number(5 * EpochDuration::get() / 2);
        let last_stored_block = 69;

        for i in 0..3 {
            assert_ok!(do_block_commitment(
                session_index,
                network_id,
                i,
                last_stored_block,
            ));
        }

        let current_block = SlowClap::current_block_number();
        let offences = Offences::get();
        assert_eq!(offences.len(), 0);

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

        let offences = Offences::get();
        assert_eq!(offences.len(), 1);
        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.offence_type, OffenceType::CommitmentOffence);
        }

        let block_commitments = BlockCommitments::<Runtime>::get(network_id);
        assert_eq!(block_commitments.get(&3), None);
        block_commitments.values().for_each(|details| {
            assert_eq!(details.last_stored_block, last_stored_block);
            assert_eq!(details.last_updated, current_block);
            assert_eq!(details.commits, 1);
        });
    });
}

#[test]
fn should_check_different_networks_during_on_initialize() {
    let times = 69;
    let networks_count = 3;

    let mut check_commitment_events = HashMap::new();

    new_test_ext().execute_with(|| {
        let _ = advance_session_and_get_index();
        for network_id in 0..networks_count {
            prepare_evm_network(Some(network_id), None);
        }

        let expected_events_count: u64 = BLOCK_CHECK_CYCLES
            .saturating_sub(1)
            .saturating_mul(networks_count as u64)
            .saturating_mul(times);

        let expected_events_count: usize = expected_events_count.try_into().unwrap();

        let epochs_passed = times * EpochDuration::get();
        for check_block in 0..epochs_passed {
            SlowClap::on_initialize(check_block);
        }

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

        binding
            .iter()
            .map(|record| record.event.clone())
            .for_each(|event| {
                if let RuntimeEvent::SlowClap(crate::Event::BlockCommitmentsCheck {
                    network_id,
                    ..
                }) = event
                {
                    *check_commitment_events.entry(network_id).or_insert(0) += 1;
                }
            });

        assert_eq!(total_number_of_events, expected_events_count);
        assert_eq!(check_commitment_events.len() as u32, networks_count);
        for network_id in 0..networks_count {
            let network_id_count = check_commitment_events.get(&network_id).unwrap();
            let expected_events_count =
                expected_events_count.saturating_div(networks_count as usize);
            assert_eq!(*network_id_count, expected_events_count);
        }
    });
}

#[test]
fn should_check_responses_correctly() {
    new_test_ext().execute_with(|| {
        let empty_responses = vec![];
        assert_err!(
            SlowClap::check_evm_responses_correctness(&empty_responses),
            OffchainErr::EmptyResponses,
        );

        let different_responses_types = vec![
            EvmResponseType::BlockNumber(60),
            EvmResponseType::BlockNumber(420),
            EvmResponseType::TransactionLogs(Default::default()),
            EvmResponseType::BlockNumber(1337),
        ];
        assert_err!(
            SlowClap::check_evm_responses_correctness(&different_responses_types),
            OffchainErr::DifferentEvmResponseTypes,
        );

        let correct_block_responses = vec![
            EvmResponseType::BlockNumber(69),
            EvmResponseType::BlockNumber(420),
            EvmResponseType::BlockNumber(1337),
            EvmResponseType::BlockNumber(20335745),
        ];
        assert_ok!(SlowClap::check_evm_responses_correctness(
            &correct_block_responses
        ));

        let correct_log_responses = vec![
            EvmResponseType::TransactionLogs(Default::default()),
            EvmResponseType::TransactionLogs(Default::default()),
            EvmResponseType::TransactionLogs(Default::default()),
            EvmResponseType::TransactionLogs(Default::default()),
        ];
        assert_ok!(SlowClap::check_evm_responses_correctness(
            &correct_log_responses
        ));
    });
}

#[test]
fn should_get_balanced_responses_correctly() {
    new_test_ext().execute_with(|| {
        let max_distance = 420;
        let empty_responses = vec![];
        assert_err!(
            SlowClap::get_balanced_evm_response(&empty_responses, max_distance),
            OffchainErr::EmptyResponses,
        );

        let correct_block_responses = vec![
            EvmResponseType::BlockNumber(69),
            EvmResponseType::BlockNumber(420),
            EvmResponseType::BlockNumber(422),
            EvmResponseType::BlockNumber(1337),
        ];
        let median_block =
            SlowClap::get_balanced_evm_response(&correct_block_responses, max_distance)
                .expect("median block should be extractable; qed");
        assert_eq!(median_block, EvmResponseType::BlockNumber(421));

        let contradictory_block_responses = vec![
            EvmResponseType::BlockNumber(69),
            EvmResponseType::BlockNumber(1337),
        ];
        assert_err!(
            SlowClap::get_balanced_evm_response(&contradictory_block_responses, max_distance),
            OffchainErr::ContradictoryBlockMedian(1337, 69, max_distance),
        );

        let first_correct_log = Log {
            transaction_hash: Some(H256::random()),
            block_number: Some(69),
            topics: vec![],
            removed: false,
        };
        let second_correct_log = Log {
            transaction_hash: Some(H256::random()),
            block_number: Some(420),
            topics: vec![],
            removed: false,
        };
        let first_wrong_block_log = Log {
            block_number: Some(1338),
            ..first_correct_log.clone()
        };
        let first_wrong_transaction_log = Log {
            transaction_hash: Some(H256::zero()),
            ..first_correct_log.clone()
        };
        let second_wrong_block_log = Log {
            block_number: Some(1338),
            ..second_correct_log.clone()
        };
        let second_wrong_transaction_log = Log {
            transaction_hash: Some(H256::zero()),
            ..second_correct_log.clone()
        };

        let correct_log_responses = vec![
            EvmResponseType::TransactionLogs(vec![
                first_correct_log.clone(),
                second_correct_log.clone(),
            ]),
            EvmResponseType::TransactionLogs(vec![
                first_correct_log.clone(),
                second_correct_log.clone(),
            ]),
            EvmResponseType::TransactionLogs(vec![
                first_correct_log.clone(),
                second_wrong_block_log.clone(),
            ]),
            EvmResponseType::TransactionLogs(vec![
                first_correct_log.clone(),
                second_wrong_transaction_log.clone(),
            ]),
            EvmResponseType::TransactionLogs(vec![
                first_wrong_block_log.clone(),
                second_correct_log.clone(),
            ]),
            EvmResponseType::TransactionLogs(vec![
                first_wrong_transaction_log.clone(),
                second_correct_log.clone(),
            ]),
            EvmResponseType::TransactionLogs(vec![first_wrong_block_log, second_wrong_block_log]),
            EvmResponseType::TransactionLogs(vec![
                first_wrong_transaction_log,
                second_wrong_transaction_log,
            ]),
        ];
        let best_logs = SlowClap::get_balanced_evm_response(&correct_log_responses, 420)
            .expect("best logs should be extractable; qed");
        assert_eq!(
            best_logs,
            EvmResponseType::TransactionLogs(vec![first_correct_log, second_correct_log])
        );
    });
}

#[test]
fn migration_from_v2_to_v3_works_fine() {
    new_test_ext().execute_with(|| {
        let network_ids = vec![1, 57232, 232, 775];

        StorageVersion::new(2).put::<SlowClap>();
        System::set_block_number(69);

        for network_id in network_ids.iter() {
            prepare_evm_network(Some(*network_id), None);
            let mut fake_old_commits = BTreeMap::new();

            fake_old_commits.insert(
                0,
                OldCommitmentDetails {
                    last_stored_block: (*network_id).into(),
                    last_updated: 133742069,
                    commits: 420,
                },
            );
            fake_old_commits.insert(
                2,
                OldCommitmentDetails {
                    last_stored_block: (*network_id).into(),
                    last_updated: 133742069,
                    commits: 5,
                },
            );

            let key_hash = BlockCommitments::<Runtime>::hashed_key_for(&network_id);
            frame_support::storage::unhashed::put(&key_hash, &fake_old_commits);
        }

        type Migrate = crate::migrations::MigrateV2ToV3<Runtime>;
        <Migrate as frame_support::traits::OnRuntimeUpgrade>::on_runtime_upgrade();

        let current_block_number = System::current_block_number();

        for network_id in network_ids.iter() {
            BlockCommitments::<Runtime>::get(&network_id)
                .iter()
                .for_each(|(authority_id, details)| match authority_id {
                    0 => {
                        assert_eq!(details.last_stored_block, *network_id as u64);
                        assert_eq!(details.last_updated, current_block_number);
                        assert_eq!(details.commits, u8::MAX);
                    }
                    2 => {
                        assert_eq!(details.last_stored_block, *network_id as u64);
                        assert_eq!(details.last_updated, current_block_number);
                        assert_eq!(details.commits, 5);
                    }
                    _ => panic!("non registered block commitment exists after migration"),
                });
        }
    });
}

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,
        network_curve: ghost_networks::NetworkCurve::Secp256k1,
        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,
    last_stored_block: ExternalBlockNumber,
) -> dispatch::DispatchResult {
    let block_commitment = BlockCommitment {
        session_index,
        authority_index,
        network_id,
        last_stored_block,
    };
    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()),
            ("User-Agent".to_string(), "curl/8.9.0".to_string()),
        ],
        response: Some(b"{\"id\":0,\"jsonrpc\":\"2.0\",\"result\":\"0x1364c81\"}".to_vec()),
        body: expected_body.clone(),
        sent: true,
        ..Default::default()
    });

    state.expect_request(testing::PendingRequest {
        method: "POST".into(),
        uri: "https://other.endpoint.network.com".into(),
        headers: vec![
            ("Accept".to_string(), "application/json".to_string()),
            ("Content-Type".to_string(), "application/json".to_string()),
            ("User-Agent".to_string(), "curl/8.9.0".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()),
            ("User-Agent".to_string(), "curl/8.9.0".to_string()),
        ],
        response_headers: vec![
            ("Accept".to_string(), "application/json".to_string()),
            ("Content-Type".to_string(), "application/json".to_string()),
        ],
        body: expected_body.clone(),
        response: Some(expected_response.clone()),
        sent: true,
        ..Default::default()
    });

    state.expect_request(testing::PendingRequest {
        method: "POST".into(),
        uri: "https://other.endpoint.network.com".into(),
        headers: vec![
            ("Accept".to_string(), "application/json".to_string()),
            ("Content-Type".to_string(), "application/json".to_string()),
            ("User-Agent".to_string(), "curl/8.9.0".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()
    });
}
