// Ensure we're `no_std` when compiling for Wasm.
#![cfg_attr(not(feature = "std"), no_std)]

use codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use serde::{Deserializer, Deserialize};

pub use pallet::*;
use frame_support::{
    pallet_prelude::*,
    traits::{
        tokens::{
            fungible::{Inspect, Mutate},
            Preservation, Precision, Fortitude,
        },
        EstimateNextSessionRotation, ValidatorSet, ValidatorSetWithIdentification, 
        OneSessionHandler, Get, 
    },
    PalletId, BoundedSlice, BoundedVec, WeakBoundedVec,
    
};
use frame_system::{
    offchain::{SendTransactionTypes, SubmitTransaction},
    pallet_prelude::*,
};

use sp_core::{H160, H256};
use sp_runtime::{
    traits::{
        TrailingZeroInput, Saturating, BlockNumberProvider, Convert,
        AccountIdConversion,
    },
    offchain as rt_offchain,
    offchain::{
        HttpError,
        storage::StorageValueRef,
        storage_lock::{BlockAndTime, StorageLock},
    },
    RuntimeAppPublic, Perbill, RuntimeDebug, SaturatedConversion,
};
use sp_std::{
    vec::Vec, prelude::*,
    collections::btree_map::BTreeMap, 
};
use sp_staking::{
    offence::{Kind, Offence, ReportOffence},
    SessionIndex,
};
use sp_io::hashing::keccak_256;

use ghost_networks::{
    NetworkData, NetworkDataBasicHandler, NetworkDataInspectHandler,
    NetworkDataMutateHandler,
};

pub mod weights;
pub use crate::weights::WeightInfo;
mod tests;
mod mock;
mod benchmarking;

pub mod sr25519 {
    mod app_sr25519 {
        use sp_application_crypto::{app_crypto, sr25519, KeyTypeId};
        const SLOW_CLAP: KeyTypeId = KeyTypeId(*b"slow");
        app_crypto!(sr25519, SLOW_CLAP);
    }

    sp_application_crypto::with_pair! {
        /// A staking keypair sr25519 at its crypto.
        pub type AuthorityPair = app_sr25519::Pair;
    }

    /// A staking signature using sr25519 as its crypto.
    pub type AuthoritySignature = app_sr25519::Signature;
    /// A staking identifier using sr25519 as its crypto.
    pub type AuthorityId = app_sr25519::Public;
}

const LOG_TARGET: &str = "runtime::ghost-slow-clap";
const DB_PREFIX: &[u8] = b"slow_clap::";

const FETCH_TIMEOUT_PERIOD: u64 = 3_000;
const LOCK_BLOCK_EXPIRATION: u32 = 5;
const LOCK_TIMEOUT_EXPIRATION: u64 = FETCH_TIMEOUT_PERIOD + 1_000;

const PERCENT_DIVISOR: u32 = 4;
const COMPANIONS_LIMIT: usize = 20;

pub type CompanionId = u128;
pub type AuthIndex = u32;

#[derive(RuntimeDebug, Clone, PartialEq, Deserialize, Encode, Decode)]
struct EvmResponse {
    #[serde(default)]
    id: Option<u32>,
    #[serde(default, deserialize_with = "de_string_to_bytes")]
    jsonrpc: Option<Vec<u8>>,
    #[serde(default, deserialize_with = "de_string_to_bytes")]
    error: Option<Vec<u8>>,
    #[serde(default)]
    result: Option<EvmResponseType>,
}

#[derive(RuntimeDebug, Clone, PartialEq, Deserialize, Encode, Decode)]
#[serde(untagged)]
enum EvmResponseType {
    #[serde(deserialize_with = "de_string_to_u64_pure")]
    BlockNumber(u64),
    TransactionLogs(Vec<Log>),
}

#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Deserialize, Encode, Decode)]
#[serde(rename_all = "camelCase")]
struct Log {
    #[serde(default, deserialize_with = "de_string_to_h256")]
    transaction_hash: Option<H256>,
    #[serde(default, deserialize_with = "de_string_to_u64")]
    block_number: Option<u64>,
    #[serde(default, deserialize_with = "de_string_to_vec_of_bytes")]
    topics: Vec<Vec<u8>>,
    #[serde(default, deserialize_with = "de_string_to_bytes")]
    address: Option<Vec<u8>>,
    #[serde(default, deserialize_with = "de_string_to_btree_map")]
    data: BTreeMap<u128, u128>,
    removed: bool,
}

pub fn de_string_to_bytes<'de, D>(de: D) -> Result<Option<Vec<u8>>, D::Error>
where D: Deserializer<'de> {
    let s: &str = Deserialize::deserialize(de)?;
    Ok(Some(s.as_bytes().to_vec()))
}

pub fn de_string_to_u64<'de, D>(de: D) -> Result<Option<u64>, D::Error>
where D: Deserializer<'de> {
    let s: &str = Deserialize::deserialize(de)?;
    let s = if s.starts_with("0x") { &s[2..] } else { &s };
    Ok(u64::from_str_radix(s, 16).ok())
}

pub fn de_string_to_u64_pure<'de, D>(de: D) -> Result<u64, D::Error>
where D: Deserializer<'de> {
    let s: &str = Deserialize::deserialize(de)?;
    let s = if s.starts_with("0x") { &s[2..] } else { &s };
    Ok(u64::from_str_radix(s, 16).unwrap_or_default())
}

pub fn de_string_to_h256<'de, D>(de: D) -> Result<Option<H256>, D::Error>
where D: Deserializer<'de> {
    let s: &str = Deserialize::deserialize(de)?;
    let start_index = if s.starts_with("0x") { 2 } else { 0 };
    let h256: Vec<_> = (start_index..s.len())
        .step_by(2)
        .map(|i| u8::from_str_radix(&s[i..i+2], 16).expect("valid u8 symbol; qed"))
        .collect();
    Ok(Some(H256::from_slice(&h256)))
}

pub fn de_string_to_vec_of_bytes<'de, D>(de: D) -> Result<Vec<Vec<u8>>, D::Error>
where D: Deserializer<'de> {
    let strings: Vec<&str> = Deserialize::deserialize(de)?;
    Ok(strings
        .iter()
        .map(|s| {
            let start_index = if s.starts_with("0x") { 2 } else { 0 };
            (start_index..s.len())
                .step_by(2)
                .map(|i| u8::from_str_radix(&s[i..i+2], 16).expect("valid u8 symbol; qed"))
                .collect::<Vec<u8>>()
        })
        .collect::<Vec<Vec<u8>>>())
}

pub fn de_string_to_btree_map<'de, D>(de: D) -> Result<BTreeMap<u128, u128>, D::Error>
where D: Deserializer<'de> {
    let s: &str = Deserialize::deserialize(de)?;
    let start_index = if s.starts_with("0x") { 2 } else { 0 };
    Ok(BTreeMap::from_iter((start_index..s.len())
            .step_by(64)
            .map(|i| (
                    u128::from_str_radix(&s[i..i+32], 16).expect("valid u8 symbol; qed"),
                    u128::from_str_radix(&s[i+32..i+64], 16).expect("valid u8 symbol; qed"),
            ))))
}

#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo, MaxEncodedLen)]
pub struct Clap<AccountId, NetworkId, Balance> {
    pub session_index: SessionIndex,
    pub authority_index: AuthIndex,
    pub transaction_hash: H256,
    pub block_number: u64,
    pub removed: bool,
    pub network_id: NetworkId,
    pub receiver: AccountId,
    pub amount: Balance,
    pub companions: BTreeMap<CompanionId, Balance>,
}

#[derive(Encode, Decode, Clone, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
pub struct Companion<NetworkId, Balance> {
    pub network_id: NetworkId,
    pub receiver: H160,
    pub amount: Balance,
}

#[derive(Encode, Decode, Clone, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
pub struct SessionAuthorityInfo {
    pub actions: u32,
    pub disabled: bool,
}

#[cfg_attr(test, derive(PartialEq))]
enum OffchainErr<NetworkId> {
    FailedSigning,
    FailedToAcquireLock(NetworkId),
    SubmitTransaction,
    HttpJsonParsingError,
    HttpBytesParsingError,
    HttpRequestError(HttpError),
    RequestUncompleted,
    HttpResponseNotOk(u16),
    ErrorInEvmResponse,
    NoEvmLogsFound(NetworkId),
    OnlyPendingEvmLogs(NetworkId),
}

impl<NetworkId: core::fmt::Debug> core::fmt::Debug for OffchainErr<NetworkId> {
    fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
        match *self {
            OffchainErr::FailedSigning => write!(fmt, "Failed to sign clap."),
            OffchainErr::FailedToAcquireLock(ref network_id) => write!(fmt, "Failed to acquire lock for network #{:?}.", network_id),
            OffchainErr::SubmitTransaction => write!(fmt, "Failed to submit transaction."),
            OffchainErr::HttpJsonParsingError => write!(fmt, "Failed to parse evm response as JSON."),
            OffchainErr::HttpBytesParsingError => write!(fmt, "Failed to parse evm response as bytes."),
            OffchainErr::HttpRequestError(http_error) => match http_error {
                HttpError::DeadlineReached => write!(fmt, "Requested action couldn't been completed within a deadline."),
                HttpError::IoError => write!(fmt, "There was an IO error while processing the request."),
                HttpError::Invalid => write!(fmt, "The ID of the request is invalid in this context"),
            },
            OffchainErr::RequestUncompleted => write!(fmt, "Failed to complete request."),
            OffchainErr::HttpResponseNotOk(code) => write!(fmt, "Http response returned code {:?}", code),
            OffchainErr::ErrorInEvmResponse => write!(fmt, "Error in evm reponse."),
            OffchainErr::NoEvmLogsFound(ref network_id) => write!(fmt, "No incoming evm logs for network #{:?}.", network_id),
            OffchainErr::OnlyPendingEvmLogs(ref network_id) => write!(fmt, "Only pending evm logs for network #{:?}.", network_id),
        }
    }
}

pub type NetworkIdOf<T> = 
    <<T as Config>::NetworkDataHandler as NetworkDataBasicHandler>::NetworkId;

pub type BalanceOf<T> = <<T as Config>::Currency as Inspect<
    <T as frame_system::Config>::AccountId,
>>::Balance;

pub type ValidatorId<T> = <<T as Config>::ValidatorSet as ValidatorSet<
    <T as frame_system::Config>::AccountId,
>>::ValidatorId;

pub type IdentificationTuple<T> = (
    ValidatorId<T>,
    <<T as Config>::ValidatorSet as ValidatorSetWithIdentification<
        <T as frame_system::Config>::AccountId,
    >>::Identification,
);

type OffchainResult<T, A> = Result<A, OffchainErr<NetworkIdOf<T>>>;

#[frame_support::pallet]
pub mod pallet {
    use super::*;

    /// The current storage version.
    const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);

    #[pallet::pallet]
    #[pallet::storage_version(STORAGE_VERSION)]
    #[pallet::without_storage_info]
    pub struct Pallet<T>(_);

    #[pallet::config]
    pub trait Config: SendTransactionTypes<Call<Self>> + frame_system::Config {
        type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
        type AuthorityId: Member 
            + Parameter
            + RuntimeAppPublic
            + Ord
            + MaybeSerializeDeserialize
            + MaxEncodedLen;

        type NextSessionRotation: EstimateNextSessionRotation<BlockNumberFor<Self>>;
        type ValidatorSet: ValidatorSetWithIdentification<Self::AccountId>;
        type Currency: Inspect<Self::AccountId> + Mutate<Self::AccountId>;
        type NetworkDataHandler: NetworkDataBasicHandler + 
            NetworkDataInspectHandler<NetworkData> + 
            NetworkDataMutateHandler<NetworkData>;
        type BlockNumberProvider: BlockNumberProvider<BlockNumber = BlockNumberFor<Self>>;
        type ReportUnresponsiveness: ReportOffence<
            Self::AccountId,
            IdentificationTuple<Self>,
            ThrottlingOffence<IdentificationTuple<Self>>,
        >;

        #[pallet::constant]
        type MaxAuthorities: Get<u32>;

        #[pallet::constant]
        type MaxAuthorityInfoInSession: Get<u32>;
        
        #[pallet::constant]
        type MaxNumberOfClaps: Get<u32>;

        #[pallet::constant]
        type ApplauseThreshold: Get<u32>;

        #[pallet::constant]
        type OffenceThreshold: Get<u32>;

        #[pallet::constant]
        type UnsignedPriority: Get<TransactionPriority>;

        #[pallet::constant]
        type TreasuryPalletId: Get<PalletId>;

        type WeightInfo: WeightInfo;
    }

    #[pallet::event]
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
    pub enum Event<T: Config> {
        AllGood,
        SomeTrottling { throttling: Vec<IdentificationTuple<T>> },
        Clapped {
            authority_id: T::AuthorityId,
            network_id: NetworkIdOf<T>,
            clap_hash: H256,
            transaction_hash: H256,
            receiver: T::AccountId,
            amount: BalanceOf<T>,
        },
        Applaused {
            network_id: NetworkIdOf<T>,
            receiver: T::AccountId,
            received_amount: BalanceOf<T>,
            number_of_companions: u32,
        },
        CompanionCreated {
            companion_id: CompanionId,
            owner: T::AccountId,
            companion: Companion<NetworkIdOf<T>, BalanceOf<T>>,
        },
        CompanionReleased {
            network_id: NetworkIdOf<T>,
            companion_id: CompanionId,
            who: T::AccountId,
            release_block: BlockNumberFor<T>,
        },
        CompanionKilled {
            network_id: NetworkIdOf<T>,
            who: T::AccountId,
            companion_id: CompanionId,
            freed_balance: BalanceOf<T>,
        },
    }

    #[pallet::error]
    pub enum Error<T> {
        NotAnAuthority,
        CurrentValidatorIsDisabled,
        CouldNotHashCompanions,
        AlreadyClapped,
        CompanionAlreadyRegistered,
        CompanionDetailsAlreadyRegistered,
        CompanionDetailsNotRegistered,
        CompanionAmountNotExistent,
        CompanionAmountUnderflow,
        NoMoreCompanions,
        NotValidCompanion,
        NonRegisteredNetwork,
        ReleaseTimeHasNotCome,
    }
    
    #[pallet::storage]
    #[pallet::getter(fn received_claps)]
    pub(super) type ReceivedClaps<T: Config> = StorageDoubleMap<
        _,
        Twox64Concat,
        H256,
        Twox64Concat,
        T::AuthorityId,
        bool,
        ValueQuery
    >;
    
    #[pallet::storage]
    #[pallet::getter(fn applauses_for_transaction)]
    pub(super) type ApplausesForTransaction<T: Config> = StorageMap<
        _,
        Twox64Concat,
        H256,
        bool,
        ValueQuery
    >;

    #[pallet::storage]
    #[pallet::getter(fn authorities_claps)]
    pub(super) type AuthoritiesClaps<T: Config> = StorageDoubleMap<
        _,
        Twox64Concat,
        T::AuthorityId,
        Twox64Concat,
        H256,
        bool,
        ValueQuery,
    >;

    #[pallet::storage]
    #[pallet::getter(fn authority_info_in_session)]
    pub(super) type AuthorityInfoInSession<T: Config> = StorageMap<
        _,
        Twox64Concat,
        SessionIndex,
        BoundedVec<SessionAuthorityInfo, T::MaxAuthorityInfoInSession>,
        ValueQuery,
    >;

    #[pallet::storage]
    #[pallet::getter(fn companions)]
    pub(super) type Companions<T: Config> = StorageDoubleMap<
        _,
        Twox64Concat,
        NetworkIdOf<T>,
        Twox64Concat,
        CompanionId,
        T::AccountId,
        OptionQuery,
    >;


    #[pallet::storage]
    #[pallet::getter(fn companion_details)]
    pub(super) type CompanionDetails<T: Config> = StorageMap<
        _,
        Twox64Concat,
        CompanionId,
        Companion<NetworkIdOf<T>, BalanceOf<T>>,
        OptionQuery,
    >;

    #[pallet::storage]
    #[pallet::getter(fn release_blocks)]
    pub(super) type ReleaseBlocks<T: Config> = StorageMap<
        _,
        Twox64Concat,
        CompanionId,
        BlockNumberFor<T>,
        ValueQuery,
    >;

    #[pallet::storage]
    #[pallet::getter(fn current_companion_id)]
    pub(super) type CurrentCompanionId<T: Config> = 
        StorageValue<_, CompanionId, ValueQuery>;

    #[pallet::storage]
    #[pallet::getter(fn keys)]
    pub(super) type Authorities<T: Config> = 
        StorageValue<_, WeakBoundedVec<T::AuthorityId, T::MaxAuthorities>, ValueQuery>;

    #[pallet::genesis_config]
    #[derive(frame_support::DefaultNoBound)]
    pub struct GenesisConfig<T: Config> {
        pub keys: Vec<T::AuthorityId>,
    }

    #[pallet::genesis_build]
    impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
        fn build(&self) {
            Pallet::<T>::initialize_authorities(&self.keys);
            Pallet::<T>::restart_clap_round(1, self.keys.len());
        }
    }

    #[pallet::call]
    impl<T: Config> Pallet<T> {
        #[pallet::call_index(0)]
        #[pallet::weight((
            T::WeightInfo::slow_clap(
                claps.len() as u32,
                claps.iter().fold(0u32, |acc, c| {
                    acc + c.companions.len() as u32
                })
            ), 
            frame_support::dispatch::Pays::No,
        ))]
        pub fn slow_clap(
            origin: OriginFor<T>,
            claps: Vec<Clap<T::AccountId, NetworkIdOf<T>, BalanceOf<T>>>,
            // since signature verification is done in `validate_unsigned`
            // we can skip doing it here again.
            _signature: <T::AuthorityId as RuntimeAppPublic>::Signature,
        ) -> DispatchResult {
            ensure_none(origin)?;
            Self::clap_if_possible(claps)?;
            Ok(())
        }

        #[pallet::call_index(1)]
        #[pallet::weight(T::WeightInfo::propose_companion())]
        pub fn propose_companion(
            origin: OriginFor<T>,
            network_id: NetworkIdOf<T>,
            companion: Companion<NetworkIdOf<T>, BalanceOf<T>>,
        ) -> DispatchResult {
            let who = ensure_signed(origin)?;
            let companion_id = CurrentCompanionId::<T>::get();

            ensure!(companion.amount > T::Currency::minimum_balance(),
                Error::<T>::CompanionAmountNotExistent);
            ensure!(T::NetworkDataHandler::get(&network_id).is_some(),
                Error::<T>::NonRegisteredNetwork);

            T::Currency::burn_from(
                &who, 
                companion.amount, 
                Preservation::Expendable,
                Precision::Exact,
                Fortitude::Force,
            )?;

            Companions::<T>::set(network_id, companion_id, Some(who.clone()));
            CompanionDetails::<T>::set(companion_id, Some(companion.clone()));
            CurrentCompanionId::<T>::set(companion_id
                .checked_add(1)
                .ok_or(Error::<T>::NoMoreCompanions)?);

            Self::deposit_event(Event::<T>::CompanionCreated {
                companion_id,
                owner: who,
                companion,
            });

            Ok(())
        }

        #[pallet::call_index(2)]
        #[pallet::weight(T::WeightInfo::release_companion())]
        pub fn release_companion(
            origin: OriginFor<T>,
            network_id: NetworkIdOf<T>,
            companion_id: CompanionId,
        ) -> DispatchResult {
            let who = ensure_signed(origin)?;

            let owner = Companions::<T>::get(network_id, companion_id);
            let network = T::NetworkDataHandler::get(&network_id);

            ensure!(owner.is_some_and(|o| o == who), Error::<T>::NotValidCompanion);
            ensure!(CompanionDetails::<T>::get(companion_id).is_some(),
                Error::<T>::CompanionDetailsNotRegistered);
            
            let offset = T::BlockNumberProvider::current_block_number() +
                network
                .ok_or(Error::<T>::NonRegisteredNetwork)?
                .release_delay
                .unwrap_or_default()
                .saturated_into::<BlockNumberFor<T>>();

            ReleaseBlocks::<T>::set(companion_id, offset);

            Self::deposit_event(Event::<T>::CompanionReleased {
                network_id,
                companion_id,
                who,
                release_block: offset,
            });

            Ok(())
        }

        #[pallet::call_index(3)]
        #[pallet::weight(T::WeightInfo::kill_companion())]
        pub fn kill_companion(
            origin: OriginFor<T>,
            network_id: NetworkIdOf<T>,
            companion_id: CompanionId,
        ) -> DispatchResult {
            let who = ensure_signed(origin)?;

            ensure!(ReleaseBlocks::<T>::get(companion_id) <=
                T::BlockNumberProvider::current_block_number(),
                Error::<T>::ReleaseTimeHasNotCome);

            ensure!(Companions::<T>::get(network_id, companion_id).is_some_and(|o| o == who), 
                Error::<T>::NotValidCompanion);

            let companion = CompanionDetails::<T>::get(companion_id)
                .ok_or(Error::<T>::CompanionDetailsNotRegistered)?;

            Companions::<T>::remove(network_id, companion_id);
            CompanionDetails::<T>::remove(companion_id);

            T::Currency::mint_into(&who, companion.amount)?;

            Self::deposit_event(Event::<T>::CompanionKilled {
                network_id,
                who,
                companion_id,
                freed_balance: companion.amount,
            });

            Ok(())
        }
    }

    #[pallet::hooks]
    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
        fn offchain_worker(now: BlockNumberFor<T>) {
            // Only send messages of we are a potential validator.
            if sp_io::offchain::is_validator() {
                let networks_len = T::NetworkDataHandler::iter().count();
                if networks_len == 0 { 
                    log::info!(
                        target: LOG_TARGET,
                        "👏 Skipping slow clap at {:?}: no evm networks",
                        now,
                    );
                } else {
                    for result in Self::start_slow_clapping(now, networks_len).into_iter().flatten() {
                        if let Err(e) = result {
                            log::debug!(
                                target: LOG_TARGET,
                                "👏 Skipping slow clap at {:?}: {:?}",
                                now, 
                                e,
                            );
                        }
                    }
                }
            } else {
                log::info!(
                    target: LOG_TARGET,
                    "🤥 Not a validator, skipping slow clap at {:?}.",
                    now,
                );
            }
        }
    }

    #[pallet::validate_unsigned]
    impl<T: Config> ValidateUnsigned for Pallet<T> {
        type Call = Call<T>;

        fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
            if let Call::slow_clap { claps, signature } = call {
                if claps.is_empty() || claps.len() > T::MaxNumberOfClaps::get() as usize {
                    return InvalidTransaction::BadProof.into();
                }

                if claps
                    .iter()
                    .fold(0usize, |max_len, c| {
                        usize::max(max_len, c.companions.len())
                    }) > COMPANIONS_LIMIT {
                    return InvalidTransaction::BadProof.into();
                }

                let authority_index = match &claps[..] {
                    [head, tail @ ..] => tail
                        .iter()
                        .all(|x| x.authority_index == head.authority_index)
                        .then(|| head.authority_index),
                    _ => None,
                };

                if authority_index.is_none() {
                    return InvalidTransaction::BadProof.into();
                }

                let authority_index = authority_index
                    .expect("authority index info already checked; qed");

                let authorities = Authorities::<T>::get();
                let authority = match authorities.get(authority_index as usize) {
                    Some(id) => id,
                    None => return InvalidTransaction::BadProof.into(),
                };

                let signature_valid = claps.using_encoded(|encoded_claps| {
                    authority.verify(&encoded_claps, signature)
                });

                if !signature_valid {
                    return InvalidTransaction::BadProof.into();
                }

                ValidTransaction::with_tag_prefix("SlowClap")
                    .priority(T::UnsignedPriority::get())
                    .and_provides(authority)
                    .longevity(LOCK_BLOCK_EXPIRATION.saturated_into::<u64>())
                    .propagate(true)
                    .build()
            } else {
                InvalidTransaction::Call.into()
            }
        }
    }
}

impl<T: Config> Pallet<T> {
    fn treasury_account_id() -> T::AccountId {
        T::TreasuryPalletId::get().into_account_truncating()
    }

    fn clap_if_possible(
        claps: Vec<Clap<T::AccountId, NetworkIdOf<T>, BalanceOf<T>>>, 
    ) -> DispatchResult {
        let current_authorities = Authorities::<T>::get();
        for clap in claps.iter() {
            let Ok(mut clap_str) = serde_json::to_vec(&clap.companions) else {
                continue;
            };
            clap_str.extend(clap.transaction_hash.to_fixed_bytes());
            clap_str.extend(clap.network_id.encode());
            let clap_hash = H256::from_slice(&keccak_256(&clap_str)[..]);

            let Some(public) = current_authorities.get(clap.authority_index as usize) else {
                continue;
            };


            let mut claps_in_session = AuthorityInfoInSession::<T>::get(&clap.session_index);
            if let Some(_) = claps_in_session.get(clap.authority_index as usize) {
                if claps_in_session[clap.authority_index as usize].disabled {
                    continue;
                }

                let received_clap = ReceivedClaps::<T>::get(&clap_hash, &public); 
                match (received_clap, clap.removed) {
                    (true, false)  => ReceivedClaps::<T>::set(&clap_hash, &public, false),
                    (false, true)  => ReceivedClaps::<T>::set(&clap_hash, &public, true),
                    _ => continue,
                }

                claps_in_session[clap.authority_index as usize].actions.saturating_inc();
                AuthorityInfoInSession::<T>::set(&clap.session_index, claps_in_session);

                Self::deposit_event(Event::<T>::Clapped {
                    authority_id: public.clone(),
                    network_id: clap.network_id,
                    clap_hash,
                    transaction_hash: clap.transaction_hash,
                    receiver: clap.receiver.clone(),
                    amount: clap.amount,
                });
            }

            Self::applause_if_possible(clap_hash, &clap, current_authorities.clone())?;
        }

        Ok(())
    }

    fn applause_if_possible(
        clap_hash: H256, 
        clap: &Clap<T::AccountId, NetworkIdOf<T>, BalanceOf<T>>, 
        current_authorities: WeakBoundedVec<T::AuthorityId, T::MaxAuthorities>,
    ) -> DispatchResult {
        if !ApplausesForTransaction::<T>::get(&clap_hash) {
            let clappers_len = ReceivedClaps::<T>::iter_prefix(&clap_hash)
                .filter(|(authority, yes_vote)| current_authorities.contains(authority) && *yes_vote)
                .count();

            let enough_authorities = Perbill::from_rational(
                clappers_len as u32,
                current_authorities.len() as u32,
            ) > Perbill::from_percent(T::ApplauseThreshold::get()); 

            if enough_authorities {
                Self::reward_companions(
                    clap.network_id, 
                    clap.receiver.clone(), 
                    clap.amount, 
                    clap.companions.clone(),
                )?;
            }
        }

        Ok(())
    }

    fn reward_companions(
        network_id: NetworkIdOf<T>,
        receiver: T::AccountId, 
        amount: BalanceOf<T>,
        companions: BTreeMap<CompanionId, BalanceOf<T>>,
    ) -> DispatchResult {
        let mut final_bridge_amount = amount;
        let mut number_of_companions: u32 = 0;

        let existential_balance = T::Currency::minimum_balance();

        for (companion_id, companion_amount) in companions.iter() {
            if final_bridge_amount < existential_balance { break; }
            number_of_companions.saturating_inc();
            CompanionDetails::<T>::mutate(companion_id, |maybe_stored_companion| {
                match maybe_stored_companion {
                    Some(stored_companion) if (stored_companion.amount >= *companion_amount) => {
                        stored_companion.amount = stored_companion
                            .amount
                            .saturating_sub(*companion_amount);
                        if stored_companion.amount > existential_balance { 
                            *maybe_stored_companion = Some(stored_companion.clone());
                        } else { 
                            *maybe_stored_companion = None;
                        }
                    },
                    _ => {
                        final_bridge_amount = final_bridge_amount
                            .saturating_sub(*companion_amount);
                    },
                }
            });
        }

        let incoming_fee = match T::NetworkDataHandler::get(&network_id) {
            Some(network_data) => network_data.incoming_fee,
            None => 0,
        };

        let gatekeeper_fees = Perbill::from_parts(incoming_fee)
            .mul_floor(final_bridge_amount);

        let final_bridge_amount = final_bridge_amount
            .saturating_sub(gatekeeper_fees);

        if gatekeeper_fees >= existential_balance {
            T::Currency::mint_into(
                &Self::treasury_account_id(), 
                gatekeeper_fees,
            )?;
        }

        if final_bridge_amount >= existential_balance {
            T::Currency::mint_into(
                &receiver, 
                final_bridge_amount,
            )?;
        }

        Self::deposit_event(Event::<T>::Applaused {
            network_id,
            receiver,
            received_amount: final_bridge_amount,
            number_of_companions,
        });

        Ok(())
    }

    fn create_key(first: Vec<u8>, second: Vec<u8>) -> Vec<u8> {
        let mut key = DB_PREFIX.to_vec();
        key.extend(first);
        key.extend(second);
        key
    }

    fn start_slow_clapping(
        block_number: BlockNumberFor<T>,
        networks_len: usize,
    ) -> OffchainResult<T, impl Iterator<Item = OffchainResult<T, ()>>> {
        let session_index = T::ValidatorSet::session_index();
        let index = block_number.into().as_u128() % networks_len as u128;
        let network_in_use = T::NetworkDataHandler::iter()
            .nth(index as usize)
            .expect("network should exist; qed");

        let network_id = network_in_use.0;
        let gatekeeper = network_in_use.1.gatekeeper;
        let topic_name = network_in_use.1.topic_name;

        let key = Self::create_key(network_id.encode(), b"endpoint".to_vec());
        let rpc_endpoint = match StorageValueRef::persistent(&key).get() {
            Ok(Some(endpoint)) => endpoint,
            _ => network_in_use.1.default_endpoint,
        };

        let finality_delay = 
            network_in_use.1.finality_delay.unwrap_or(1u64);

        Ok(Self::local_authorities().map(move |(authority_index, authority_key)| {
            Self::do_evm_claps_or_save_block(
                authority_index, 
                authority_key, 
                session_index,
                network_id, 
                finality_delay,
                gatekeeper.clone(),
                topic_name.clone(),
                rpc_endpoint.clone(),
            )
        }))
    }

    fn do_evm_claps_or_save_block(
        authority_index: AuthIndex,
        authority_key: T::AuthorityId,
        session_index: SessionIndex,
        network_id: NetworkIdOf<T>,
        finality_delay: u64,
        gatekeeper: Vec<u8>,
        topic_name: Vec<u8>,
        rpc_endpoint: Vec<u8>,
    ) -> OffchainResult<T, ()> {
        let key = Self::create_key(network_id.encode(), b"lock".to_vec());
        let mut network_lock = StorageLock::<BlockAndTime<Self>>::with_block_and_time_deadline(
            &key, 
            LOCK_BLOCK_EXPIRATION,
            rt_offchain::Duration::from_millis(LOCK_TIMEOUT_EXPIRATION),
        );

        network_lock
            .try_lock()
            .map_err(|_| OffchainErr::FailedToAcquireLock(network_id))?
            .forget();

        let key = Self::create_key(network_id.encode(), b"block".to_vec());
        let mut evm_block_info = StorageValueRef::persistent(&key);
        let evm_block: Option<u64> = match evm_block_info.get() {
            Ok(option_block) => option_block,
            _ => None,
        };

        let evm_response = Self::fetch_and_parse(
            evm_block, finality_delay,
            gatekeeper, topic_name, rpc_endpoint
        )?;

        match evm_response {
            EvmResponseType::BlockNumber(evm_block) => {
                let deviation = Self::random_u64_deviation(finality_delay);
                let evm_start_block = evm_block.saturating_sub(deviation);
                evm_block_info.set(&evm_start_block);
                log::info!(
                    target: LOG_TARGET,
                    "🧐 New evm block #{:?} found for {:?} network",
                    evm_start_block,
                    network_id,
                );
            },
            EvmResponseType::TransactionLogs(evm_logs) => {
                if evm_logs.is_empty() { 
                    return Err(OffchainErr::NoEvmLogsFound(network_id).into());
                }

                let evm_logs: Vec<_> = evm_logs
                    .iter()
                    .filter(|log| {
                        log.block_number.is_some() && 
                            log.transaction_hash.is_some() &&
                            log.topics.len() == 3 &&
                            log.data.len() <= COMPANIONS_LIMIT
                    })
                .collect();

                if evm_logs.is_empty() { 
                    return Err(OffchainErr::OnlyPendingEvmLogs(network_id));
                }

                log::info!(
                    target: LOG_TARGET,
                    "🧐 {:?} evm logs found for network #{:?}",
                    evm_logs.len(),
                    network_id,
                );

                let mut claps: Vec<_> = evm_logs
                    .iter()
                    .map(|log| Clap {
                            authority_index,
                            session_index,
                            network_id,
                            removed: log.removed,
                            receiver: T::AccountId::decode(&mut &log.topics[1][0..32])
                                .expect("32 bytes always construct an AccountId32"),
                            amount: u128::from_be_bytes(log.topics[2][16..32]
                                .try_into()
                                .expect("amount is valid hex; qed"))
                                .saturated_into::<BalanceOf<T>>(),
                            companions: log
                                .data
                                .clone()
                                .into_iter()
                                .map(|(key, value)| (
                                        key.saturated_into::<CompanionId>(),
                                        value.saturated_into::<BalanceOf<T>>(),
                                ))
                                .collect(),
                            transaction_hash: log
                                .transaction_hash
                                .clone()
                                .expect("tx hash exists; qed"),
                            block_number: log
                                .block_number
                                .expect("block number exists; qed"),
                        })
                    .collect();

                claps.sort_by(|a, b| a.block_number.cmp(&b.block_number));

                let maximum_claps = T::MaxNumberOfClaps::get() as usize;
                claps.truncate(maximum_claps);
                match claps.get(maximum_claps) {
                    Some(last_clap) => {
                        evm_block_info.set(&last_clap.block_number);
                        log::info!(
                            target: LOG_TARGET,
                            "🧐 New evm block #{:?} found for {:?} network",
                            last_clap.block_number,
                            network_id,
                        );
                    },
                    None => evm_block_info.clear(),
                }

                let signature = authority_key.sign(&claps.encode())
                    .ok_or(OffchainErr::FailedSigning)?;
                let call = Call::slow_clap { claps, signature };

                SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into())
                    .map_err(|_| OffchainErr::SubmitTransaction)?;
            }
        }
        Ok(())
    }

    fn random_u64_deviation(original: u64) -> u64 {
        let seed = sp_io::offchain::random_seed();
        let random = <u32>::decode(&mut TrailingZeroInput::new(seed.as_ref()))
            .expect("input is padded with zeroes; qed");

        let maximum = Perbill::one()
            .saturating_div(
                Perbill::from_parts(PERCENT_DIVISOR), 
                sp_runtime::Rounding::Down,
            )
            .deconstruct();

        let random = Perbill::from_parts(random % maximum);
        original.saturating_add(random.mul_ceil(original))
    }

    fn local_authorities() -> impl Iterator<Item = (u32, T::AuthorityId)> {
        let authorities = Authorities::<T>::get();
        let mut local_authorities = T::AuthorityId::all();
        local_authorities.sort();
        authorities.into_iter().enumerate().filter_map(move |(index, authority)| {
            local_authorities
                .binary_search(&authority)
                .ok()
                .map(|location| (index as u32, local_authorities[location].clone()))
        })
    }

    fn fetch_from_remote(
        rpc_endpoint: Vec<u8>, 
        request_body: Vec<u8>,
    ) -> OffchainResult<T, Vec<u8>> {
        let rpc_str = core::str::from_utf8(&rpc_endpoint)
            .expect("rpc endpoint valid str; qed");

        let body_str = core::str::from_utf8(&request_body)
            .expect("request body valid str: qed");

        let deadline = sp_io::offchain::timestamp()
            .add(rt_offchain::Duration::from_millis(FETCH_TIMEOUT_PERIOD));

        let pending = rt_offchain::http::Request::post(&rpc_str, vec![body_str])
            .add_header("ACCEPT", "APPLICATION/JSON")
            .add_header("CONTENT-TYPE", "APPLICATION/JSON")
            .deadline(deadline)
            .send()
            .map_err(|err| OffchainErr::HttpRequestError(err))?;

        let response = pending
            .try_wait(deadline)
            .map_err(|_| OffchainErr::RequestUncompleted)?
            .map_err(|_| OffchainErr::RequestUncompleted)?;

        if response.code != 200 {
            return Err(OffchainErr::HttpResponseNotOk(response.code))
        }

        Ok(response.body().collect::<Vec<u8>>())
    }

    fn prepare_request_body(
        maybe_block: Option<u64>, 
        finality_delay: u64,
        gatekeeper: Vec<u8>,
        topic_name: Vec<u8>,
    ) -> Vec<u8> {
        match maybe_block {
            Some(to_block) => {
                let from_block: u64 = to_block.saturating_sub(finality_delay);
                let convert_number_to_hex_vector = |value: u64| -> Vec<u8> {
                    let mut r = Vec::new();
                    let mut value = value;
                    loop {
                        r.push((value % 10 + 48) as u8);
                        value /= 10;
                        if value == 0 { break; }
                    }
                    r.reverse();
                    r
                };

                let mut body = b"{\"id\":0,\"jsonrpc\":\"2.0\",\"method\":\"eth_getLogs\",\"params\":[{".to_vec();
                body.extend(b"\"fromBlock\":".to_vec());
                body.extend(convert_number_to_hex_vector(from_block));
                body.extend(b",\"toBlock\":".to_vec());
                body.extend(convert_number_to_hex_vector(to_block));
                body.extend(b",\"address\":\"".to_vec());
                body.extend(gatekeeper);
                body.extend(b"\",\"topics\":[\"".to_vec());
                body.extend(topic_name);
                body.extend(b"\"]}]}".to_vec());
                body
            },
            None => b"{\"id\":0,\"jsonrpc\":\"2.0\",\"method\":\"eth_blockNumber\"}".to_vec()
        }.to_vec()
    }

    fn fetch_and_parse(
        evm_block: Option<u64>,
        finality_delay: u64,
        gatekeeper: Vec<u8>,
        topic_name: Vec<u8>,
        rpc_endpoint: Vec<u8>,
    ) -> OffchainResult<T, EvmResponseType> {
        let request_body = Self::prepare_request_body( 
            evm_block, finality_delay, gatekeeper, topic_name);

        let resp_bytes = Self::fetch_from_remote(rpc_endpoint, request_body)?;

        let resp_str = sp_std::str::from_utf8(&resp_bytes)
            .map_err(|_| OffchainErr::HttpBytesParsingError)?;

        let result: EvmResponse = serde_json::from_str(&resp_str)
            .map_err(|_| OffchainErr::HttpJsonParsingError)?;

        if result.error.is_some() {
            return Err(OffchainErr::ErrorInEvmResponse);
        }

        Ok(result.result.ok_or(OffchainErr::ErrorInEvmResponse)?)
    }

    fn is_good_actor(
        authority_index: usize, 
        session_index: SessionIndex,
        average_claps: u32,
    ) -> bool {
        if average_claps == 0 {
            return true;
        }

        let claps_in_session = AuthorityInfoInSession::<T>::get(session_index);
        match claps_in_session.get(authority_index) {
            Some(clap_in_session) => {
                let number_of_claps = &clap_in_session.actions;
                let authority_deviation = if *number_of_claps < average_claps {
                    Perbill::from_rational(average_claps - *number_of_claps, average_claps)
                } else { 
                    Perbill::from_rational(*number_of_claps - average_claps, average_claps)
                };
                authority_deviation < Perbill::from_percent(T::OffenceThreshold::get())
            },
            None => false,
        }
    }

    fn initialize_authorities(authorities: &[T::AuthorityId]) {
        if !authorities.is_empty() {
            assert!(Authorities::<T>::get().is_empty(), "Authorities are already initilized!");
            let bounded_authorities = BoundedSlice::<'_, _, T::MaxAuthorities>::try_from(authorities)
                .expect("more than the maximum number of clappers");
            Authorities::<T>::put(bounded_authorities);
        }    
    }

    fn restart_clap_round(session_index: SessionIndex, length: usize) {
        let empty_authority_info = SessionAuthorityInfo {
            actions: 0u32,
            disabled: false,
        };
        let empty_authorities_vec = 
            BoundedVec::<SessionAuthorityInfo, T::MaxAuthorityInfoInSession>::try_from(
                vec![empty_authority_info; length])
            .expect("authorities length should be correct");
        AuthorityInfoInSession::<T>::set(session_index, empty_authorities_vec);
    }

    // #[cfg(test)]
    // fn set_authorities(authorities: Vec<T::AuthorityId>) {
    //     let bounded_authorities = WeakBoundedVec::<_, T::MaxAuthorities>::try_from(authorities)
    //         .expect("more than the maximum number of clappers");
    //     Authorities::<T>::put(&bounded_authorities);
    //     Self::restart_clap_round(T::ValidatorSet::session_index(), bounded_authorities.len());
    // }
}

impl<T: Config> sp_runtime::BoundToRuntimeAppPublic for Pallet<T> {
    type Public = T::AuthorityId;
}

impl<T: Config> BlockNumberProvider for Pallet<T> {
    type BlockNumber = BlockNumberFor<T>;
    fn current_block_number() -> Self::BlockNumber {
        T::BlockNumberProvider::current_block_number()
    }
}

impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T> {
    type Key = T::AuthorityId;

    fn on_genesis_session<'a, I: 'a>(validators: I)
    where
        I: Iterator<Item = (&'a T::AccountId, T::AuthorityId)>,
    {
        let authorities = validators.map(|x| x.1).collect::<Vec<_>>();
        Self::initialize_authorities(&authorities);
        Self::restart_clap_round(1, authorities.len());
    }

    fn on_new_session<'a, I: 'a>(_changed: bool, validators: I, _queued_validators: I)
    where
        I: Iterator<Item = (&'a T::AccountId, T::AuthorityId)>,
    {
        let authorities = validators.map(|x| x.1).collect::<Vec<_>>();
        let bounded_authorities = WeakBoundedVec::<_, T::MaxAuthorities>::force_from(
            authorities,
            Some(
                "Warning: The session has more authorities than expected. \
                A runtime configuration adjustment may be needed.",
            ),
        );
        let bounded_authorities_len = (&bounded_authorities).len();
        Authorities::<T>::put(bounded_authorities);
        Self::restart_clap_round(T::ValidatorSet::session_index(), bounded_authorities_len);
    }

    fn on_before_session_ending() {
        let session_index = T::ValidatorSet::session_index();
        let validators = T::ValidatorSet::validators();
        let authorities = Authorities::<T>::get();
        let claps_in_session = AuthorityInfoInSession::<T>::get(session_index);

        let average_claps = claps_in_session
            .iter()
            .filter(|x| !x.disabled)
            .fold(0u32, |acc, x| acc + x.actions)
            .checked_div(claps_in_session.len() as u32)
            .unwrap_or_default();

        let offenders = validators
            .into_iter()
            .enumerate()
            .filter(|(index, _)| !Self::is_good_actor(*index, session_index, average_claps))
            .filter_map(|(_, id)| {
                <T::ValidatorSet as ValidatorSetWithIdentification<T::AccountId>>::IdentificationOf::convert(
                    id.clone(),
                ).map(|full_id| (id, full_id))
            })
            .collect::<Vec<IdentificationTuple<T>>>();

        if offenders.is_empty() {
            Self::deposit_event(Event::<T>::AllGood);
        } else {
            Self::deposit_event(Event::<T>::SomeTrottling { throttling: offenders.clone() });

            let validator_set_count = authorities.len() as u32;
            let offence = ThrottlingOffence { session_index, validator_set_count, offenders };
            if let Err(e) = T::ReportUnresponsiveness::report_offence(vec![], offence) {
                sp_runtime::print(e)
            }
        }
    }

    fn on_disabled(validator_index: u32) {
        let session_index = T::ValidatorSet::session_index();
        let mut claps_in_session = AuthorityInfoInSession::<T>::get(&session_index);
        if let Some(_) = claps_in_session.get(validator_index as usize) {
            claps_in_session[validator_index as usize].disabled = true;
            AuthorityInfoInSession::<T>::set(&session_index, claps_in_session);
        }
    }
}

#[derive(RuntimeDebug, TypeInfo)]
#[cfg_attr(feature = "std", derive(Clone, PartialEq, Eq))]
pub struct ThrottlingOffence<Offender> {
    /// Current session index in which we report the unresponsive validators.
    pub session_index: SessionIndex,
    /// The size pf the validator set in current session.
    pub validator_set_count: u32,
    /// Authorities that were unresponsive during the current session.
    pub offenders: Vec<Offender>
}

impl<Offender: Clone> Offence<Offender> for ThrottlingOffence<Offender> {
    const ID: Kind = *b"slow-clap:throtl";
    type TimeSlot = SessionIndex;

    fn offenders(&self) -> Vec<Offender> {
        self.offenders.clone()
    }

    fn session_index(&self) -> SessionIndex {
        self.session_index
    }

    fn validator_set_count(&self) -> u32 {
        self.validator_set_count
    }

    fn time_slot(&self) -> Self::TimeSlot {
        self.session_index
    }

    fn slash_fraction(&self, offenders_count: u32) -> Perbill {
        if let Some(threshold) = offenders_count.checked_sub(self.validator_set_count / 10 + 1) {
            let x = Perbill::from_rational(3 * threshold, self.validator_set_count);
            x.saturating_mul(Perbill::from_percent(7))
        } else {
            Perbill::default()
        }
    }
}
