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

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

use frame_system::RawOrigin;
use frame_support::traits::fungible::{Inspect, Mutate};

use crate::Pallet as SlowClap;

const MAX_CLAPS: u32 = 100;
const MAX_COMPANIONS: u32 = 20;

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

pub fn create_companions<T: Config>(
    total: usize,
    network_id: NetworkIdOf<T>,
    user_account: T::AccountId,
    fee: H256,
    receiver: H160,
) -> Result<CompanionId, &'static str> {
    T::NetworkDataHandler::register(network_id.into(), NetworkData {
        chain_name: "Ethereum".into(),
        default_endpoint: 
            "https://base-mainnet.core.chainstack.com/2fc1de7f08c0465f6a28e3c355e0cb14/".into(),
            finality_delay: Some(0),
            release_delay: Some(0),
            network_type: Default::default(),
            gatekeeper: b"0x1234567891234567891234567891234567891234".to_vec(),
            topic_name: b"0x12345678912345678912345678912345678912345678912345678912345678".to_vec(),
            incoming_fee: 0,
            outgoing_fee: 0,
    }).map_err(|_| BenchmarkError::Weightless)?;

    let mut last_companion_id = 0;
    for _ in 0..total {
        let minimum_balance = <<T as pallet::Config>::Currency>::minimum_balance();
        let balance = minimum_balance + minimum_balance;
        let companion = Companion::<NetworkIdOf::<T>, BalanceOf::<T>> { 
            network_id: network_id.into(), receiver, 
            fee, amount: balance,
        };

        let _ = <<T as pallet::Config>::Currency>::mint_into(&user_account, balance);
        let companion_id = SlowClap::<T>::current_companion_id();
        let _ = SlowClap::<T>::propose_companion(
            RawOrigin::Signed(user_account.clone()).into(), 
            network_id, 
            companion,
        )?;
        last_companion_id = companion_id;
    }
    Ok(last_companion_id)
}

pub fn create_claps<T: Config>(i: u32, j: u32) -> Result<
    (
        Vec<crate::Clap<T::AccountId, NetworkIdOf<T>, BalanceOf<T>>>,
        <T::AuthorityId as RuntimeAppPublic>::Signature,
    ), 
    &'static str,
> {
    let minimum_balance = <<T as pallet::Config>::Currency>::minimum_balance();
    let amount = minimum_balance + minimum_balance;
    let total_amount = amount.saturating_mul(j.into());
    let network_id = NetworkIdOf::<T>::default();

    let mut claps = Vec::new();
    let mut companions = BTreeMap::new();

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

    for index in 0..j { 
        companions.insert(
            index.into(), 
            amount,
        );
    }

    for _ in 0..i {
        claps.push(Clap {
            session_index: 1,
            authority_index: 0,
            network_id,
            transaction_hash: H256::repeat_byte(1u8),
            block_number: 69,
            removed: true,
            receiver: create_account::<T>(),
            amount: total_amount,
            companions: companions.clone(),
        });
    }

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

    Ok((claps, signature))
}

benchmarks! {
    slow_clap {
        let k in 1 .. MAX_CLAPS;
        let j in 1 .. MAX_COMPANIONS;

        let receiver = H160::repeat_byte(69u8);
        let fee = H256::repeat_byte(0u8);
        let user_account: T::AccountId = whitelisted_caller();
        let network_id = <<T as pallet::Config>::NetworkDataHandler as NetworkDataBasicHandler>::NetworkId::default();
        
        let (claps, signature) = create_claps::<T>(k, j)?;
        let _ = create_companions::<T>(j as usize, network_id, user_account, fee, receiver)?;
    }: _(RawOrigin::None, claps, signature)
    verify {
        let minimum_balance = <<T as pallet::Config>::Currency>::minimum_balance();
        let total_amount = (minimum_balance + minimum_balance).saturating_mul(j.into());
    }

    propose_companion {
        let receiver = H160::repeat_byte(69u8);
        let fee = H256::repeat_byte(0u8);
        let user_account: T::AccountId = whitelisted_caller();
        let network_id = <<T as pallet::Config>::NetworkDataHandler as NetworkDataBasicHandler>::NetworkId::default();
        // T::NetworkDataHandler::register(network_id.into(), NetworkData {
        //     chain_name: "Ethereum".into(),
        //     // https://nd-422-757-666.p2pify.com/0a9d79d93fb2f4a4b1e04695da2b77a7/
        //     default_endpoint: 
        //         "https://base-mainnet.core.chainstack.com/2fc1de7f08c0465f6a28e3c355e0cb14/".into(),
        //     finality_delay: Some(50),
        //     release_delay: Some(100),
        //     network_type: Default::default(),
        //     gatekeeper: b"0x1234567891234567891234567891234567891234".to_vec(),
        //     topic_name: b"0x12345678912345678912345678912345678912345678912345678912345678".to_vec(),
        //     incoming_fee: 0,
        //     outgoing_fee: 0,
        // }).map_err(|_| BenchmarkError::Weightless)?;
        let companion_id = create_companions::<T>(1, network_id, user_account.clone(), fee, receiver)?;
        let companion_id = SlowClap::<T>::current_companion_id();
        let minimum_balance = <<T as pallet::Config>::Currency>::minimum_balance();
        let balance = minimum_balance + minimum_balance;
        let companion = Companion::<NetworkIdOf::<T>, BalanceOf::<T>> { 
            network_id: network_id.into(), receiver, 
            fee, amount: balance,
        };
        let _ = <<T as pallet::Config>::Currency>::mint_into(&user_account, balance);
        assert_eq!(SlowClap::<T>::current_companion_id(), companion_id);
    }: _(RawOrigin::Signed(user_account), network_id.into(), companion)
    verify {
        assert_eq!(SlowClap::<T>::current_companion_id(), companion_id + 1);
    }

    release_companion {
        let receiver = H160::repeat_byte(69u8);
        let fee = H256::repeat_byte(0u8);
        let user_account: T::AccountId = whitelisted_caller();
        let network_id = <<T as pallet::Config>::NetworkDataHandler as NetworkDataBasicHandler>::NetworkId::default();
        let companion_id = create_companions::<T>(1, network_id, user_account.clone(), fee, receiver)?;
        assert_eq!(SlowClap::<T>::release_blocks(companion_id), BlockNumberFor::<T>::default());
    }: _(RawOrigin::Signed(user_account), network_id.into(), companion_id)
    verify {
        assert_ne!(SlowClap::<T>::release_blocks(companion_id), BlockNumberFor::<T>::default());
    }

    kill_companion {
        let receiver = H160::repeat_byte(69u8);
        let fee = H256::repeat_byte(0u8);
        let user_account: T::AccountId = whitelisted_caller();
        let network_id = <<T as pallet::Config>::NetworkDataHandler as NetworkDataBasicHandler>::NetworkId::default();
        let companion_id = create_companions::<T>(1, network_id, user_account.clone(), fee, receiver)?;
        SlowClap::<T>::release_companion(
            RawOrigin::Signed(user_account.clone()).into(), 
            network_id, 
            companion_id,
        )?;
        let block_shift =
            <<T as pallet::Config>::NetworkDataHandler as NetworkDataInspectHandler<NetworkData>>::get(&network_id)
            .unwrap()
            .release_delay
            .unwrap();
        frame_system::Pallet::<T>::set_block_number((block_shift + 1).saturated_into());
    }: _(RawOrigin::Signed(user_account), network_id.into(), companion_id)
    verify {
        assert_eq!(SlowClap::<T>::companions(network_id, companion_id), None);
        assert_eq!(SlowClap::<T>::companion_details(companion_id), None);
        assert_eq!(SlowClap::<T>::current_companion_id(), companion_id + 1);
    }

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