#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::large_enum_variant)]
#![allow(clippy::too_many_arguments)]

use frame_support::{
    pallet_prelude::*,
    storage::PrefixIterator,
    traits::{tokens::fungible::Inspect, EnsureOrigin},
};
use frame_system::pallet_prelude::*;
use scale_info::TypeInfo;

use sp_runtime::{
    curve::PiecewiseLinear,
    traits::{AtLeast32BitUnsigned, CheckedAdd, CheckedSub, Member, UniqueSaturatedInto},
    DispatchResult,
};
use sp_std::{convert::TryInto, prelude::*};

pub use ghost_traits::networks::{
    NetworkDataBasicHandler, NetworkDataInspectHandler, NetworkDataMutateHandler,
};

pub mod migrations;
mod weights;

pub use crate::weights::WeightInfo;
pub use module::*;

#[cfg(any(feature = "runtime-benchmarks", test))]
mod benchmarking;
#[cfg(all(feature = "std", test))]
mod mock;
#[cfg(all(feature = "std", test))]
mod tests;

const LOG_TARGET: &str = "runtime::ghost-networks";

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

#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub enum NetworkType {
    Evm = 0,
    Utxo = 1,
    Undefined = 2,
}

impl Default for NetworkType {
    fn default() -> Self {
        NetworkType::Evm
    }
}

#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub struct NetworkData {
    pub chain_name: Vec<u8>,
    pub default_endpoints: Vec<Vec<u8>>,
    pub gatekeeper: Vec<u8>,
    pub topic_name: Vec<u8>,
    pub network_type: NetworkType,
    pub finality_delay: u64,
    pub rate_limit_delay: u64,
    pub block_distance: u64,
    pub avg_block_speed: u64,
    pub incoming_fee: u32,
    pub outgoing_fee: u32,
}

#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub struct BridgeAdjustment<Balance> {
    pub bridged_out: Balance,
    pub bridged_in: Balance,
}

pub struct BridgedInflationCurve<RewardCurve, T>(core::marker::PhantomData<(RewardCurve, T)>);
impl<Balance, RewardCurve, T> pallet_staking::EraPayout<Balance>
    for BridgedInflationCurve<RewardCurve, T>
where
    Balance: Default
        + Copy
        + From<BalanceOf<T>>
        + AtLeast32BitUnsigned
        + num_traits::ops::wrapping::WrappingAdd
        + num_traits::ops::overflowing::OverflowingAdd
        + sp_std::ops::AddAssign
        + sp_std::ops::Not<Output = Balance>
        + sp_std::ops::Shl<Output = Balance>
        + sp_std::ops::Shr<Output = Balance>
        + sp_std::ops::BitAnd<Balance, Output = Balance>,
    RewardCurve: Get<&'static PiecewiseLinear<'static>>,
    T: Config,
{
    fn era_payout(
        total_staked: Balance,
        total_issuance: Balance,
        _era_duration_in_millis: u64,
    ) -> (Balance, Balance) {
        let reward_curve = RewardCurve::get();
        let bridged_imbalance = BridgedImbalance::<T>::take();
        let accumulated_commission = AccumulatedCommission::<T>::take();

        let accumulated_commission: Balance = accumulated_commission.into();
        let adjusted_issuance: Balance = total_issuance
            .saturating_add(bridged_imbalance.bridged_out.into())
            .saturating_sub(bridged_imbalance.bridged_in.into());

        let estimated_reward =
            reward_curve.calculate_for_fraction_times_denominator(total_staked, adjusted_issuance);

        let payout: Balance = sp_runtime::helpers_128bit::multiply_by_rational_with_rounding(
            estimated_reward.unique_saturated_into(),
            accumulated_commission.unique_saturated_into(),
            adjusted_issuance.unique_saturated_into(),
            sp_runtime::Rounding::NearestPrefUp,
        )
        .map(|result| result.unique_saturated_into())
        .unwrap_or_default();

        let rest_payout = accumulated_commission.saturating_sub(payout);

        (payout, rest_payout)
    }
}

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

    const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);

    #[pallet::config]
    pub trait Config: frame_system::Config {
        type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;

        /// The type used for the internal balance storage.
        type Currency: Inspect<Self::AccountId>;

        /// The type used as a unique network id.
        type NetworkId: Parameter
            + Member
            + Parameter
            + AtLeast32BitUnsigned
            + Default
            + Copy
            + Ord
            + TypeInfo
            + MaybeSerializeDeserialize
            + MaxEncodedLen;

        /// The origin required to register new network.
        type RegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;
        /// The origin required to update network information.
        type UpdateOrigin: EnsureOrigin<Self::RuntimeOrigin>;
        /// The origin required to remove network.
        type RemoveOrigin: EnsureOrigin<Self::RuntimeOrigin>;

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

        /// Weight information for extrinsics in this module.
        type WeightInfo: WeightInfo;
    }

    #[pallet::error]
    pub enum Error<T> {
        /// Network already registered.
        NetworkAlreadyRegistered,
        /// Network does not exist.
        NetworkDoesNotExist,
        /// Gatekeeper address length not 42 or prefix `0x` missed.
        WrongGatekeeperAddress,
        /// Topic name length not 66 or prefix `0x` missed.
        WrongTopicName,
        /// Could not store networks into bounded vector.
        TooManyNetworks,
    }

    #[pallet::event]
    #[pallet::generate_deposit(pub(crate) fn deposit_event)]
    pub enum Event<T: Config> {
        NetworkRegistered {
            chain_id: T::NetworkId,
            network: NetworkData,
        },
        NetworkNameUpdated {
            chain_id: T::NetworkId,
            chain_name: Vec<u8>,
        },
        NetworkEndpointUpdated {
            chain_id: T::NetworkId,
            index: u32,
            endpoint: Vec<u8>,
        },
        NetworkEndpointRemoved {
            chain_id: T::NetworkId,
            index: u32,
        },
        NetworkEndpointAdded {
            chain_id: T::NetworkId,
            endpoint: Vec<u8>,
        },
        NetworkFinalityDelayUpdated {
            chain_id: T::NetworkId,
            finality_delay: u64,
        },
        NetworkRateLimitDelayUpdated {
            chain_id: T::NetworkId,
            rate_limit_delay: u64,
        },
        NetworkBlockDistanceUpdated {
            chain_id: T::NetworkId,
            block_distance: u64,
        },
        NetworkTypeUpdated {
            chain_id: T::NetworkId,
            network_type: NetworkType,
        },
        NetworkGatekeeperUpdated {
            chain_id: T::NetworkId,
            gatekeeper: Vec<u8>,
        },
        NetworkTopicNameUpdated {
            chain_id: T::NetworkId,
            topic_name: Vec<u8>,
        },
        NetworkIncomingFeeUpdated {
            chain_id: T::NetworkId,
            incoming_fee: u32,
        },
        NetworkOutgoingFeeUpdated {
            chain_id: T::NetworkId,
            outgoing_fee: u32,
        },
        NetworkAvgBlockSpeedUpdated {
            chain_id: T::NetworkId,
            avg_block_speed: u64,
        },
        NetworkRemoved {
            chain_id: T::NetworkId,
        },
    }

    #[pallet::storage]
    #[pallet::getter(fn bridged_imbalance)]
    pub type BridgedImbalance<T: Config> =
        StorageValue<_, BridgeAdjustment<BalanceOf<T>>, ValueQuery>;

    #[pallet::storage]
    #[pallet::getter(fn accumulated_commission)]
    pub type AccumulatedCommission<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;

    #[pallet::storage]
    #[pallet::getter(fn network_indexes)]
    pub type NetworkIndexes<T: Config> =
        StorageValue<_, BoundedVec<T::NetworkId, T::MaxNetworks>, ValueQuery>;

    #[pallet::storage]
    #[pallet::getter(fn networks)]
    pub type Networks<T: Config> =
        StorageMap<_, Twox64Concat, T::NetworkId, NetworkData, OptionQuery>;

    #[pallet::storage]
    #[pallet::getter(fn gatekeeper_amount)]
    pub type GatekeeperAmount<T: Config> =
        StorageMap<_, Twox64Concat, T::NetworkId, BalanceOf<T>, ValueQuery>;

    #[pallet::genesis_config]
    pub struct GenesisConfig<T: Config> {
        pub networks: Vec<(T::NetworkId, Vec<u8>, BalanceOf<T>)>,
    }

    impl<T: Config> Default for GenesisConfig<T> {
        fn default() -> Self {
            Self { networks: vec![] }
        }
    }

    #[pallet::genesis_build]
    impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
        fn build(&self) {
            if !self.networks.is_empty() {
                self.networks
                    .iter()
                    .for_each(|(chain_id, network_metadata, gatekeeper_amount)| {
                        let network = NetworkData::decode(&mut &network_metadata[..])
                            .expect("Error decoding NetworkData");
                        Pallet::<T>::do_register_network(chain_id.clone(), network)
                            .expect("Error registering network");
                        GatekeeperAmount::<T>::insert(chain_id, gatekeeper_amount);
                    });
            }
        }
    }

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

    #[pallet::call]
    impl<T: Config> Pallet<T> {
        #[pallet::call_index(0)]
        #[pallet::weight(T::WeightInfo::register_network(
            network.chain_name.len() as u32,
            network.default_endpoints
                .iter()
                .map(|endpoint| endpoint.len())
                .max()
                .unwrap_or_default() as u32,
            network.default_endpoints.len() as u32,
        ))]
        pub fn register_network(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
            network: NetworkData,
        ) -> DispatchResult {
            T::RegisterOrigin::ensure_origin_or_root(origin)?;
            Self::do_register_network(chain_id, network)
        }

        #[pallet::call_index(1)]
        #[pallet::weight(T::WeightInfo::update_network_name(
            chain_name.len() as u32,
        ))]
        pub fn update_network_name(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
            chain_name: Vec<u8>,
        ) -> DispatchResult {
            T::UpdateOrigin::ensure_origin_or_root(origin)?;
            Self::do_update_network_name(chain_id, chain_name)
        }

        #[pallet::call_index(2)]
        #[pallet::weight(T::WeightInfo::update_network_endpoint(
            maybe_endpoint
                .as_ref()
                .map(|endpoint| endpoint.len())
                .unwrap_or_default() as u32
        ))]
        pub fn update_network_endpoint(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
            maybe_index: Option<u32>,
            maybe_endpoint: Option<Vec<u8>>,
        ) -> DispatchResult {
            T::UpdateOrigin::ensure_origin_or_root(origin)?;
            Self::do_update_network_endpoint(chain_id, maybe_index, maybe_endpoint)
        }

        #[pallet::call_index(3)]
        #[pallet::weight(T::WeightInfo::update_network_finality_delay())]
        pub fn update_network_finality_delay(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
            finality_delay: u64,
        ) -> DispatchResult {
            T::UpdateOrigin::ensure_origin_or_root(origin)?;
            Self::do_update_network_finality_delay(chain_id, finality_delay)
        }

        #[pallet::call_index(4)]
        #[pallet::weight(T::WeightInfo::update_network_rate_limit_delay())]
        pub fn update_network_rate_limit_delay(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
            rate_limit_delay: u64,
        ) -> DispatchResult {
            T::UpdateOrigin::ensure_origin_or_root(origin)?;
            Self::do_update_network_rate_limit_delay(chain_id, rate_limit_delay)
        }

        #[pallet::call_index(5)]
        #[pallet::weight(T::WeightInfo::update_network_block_distance())]
        pub fn update_network_block_distance(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
            block_distance: u64,
        ) -> DispatchResult {
            T::UpdateOrigin::ensure_origin_or_root(origin)?;
            Self::do_update_network_block_distance(chain_id, block_distance)
        }

        #[pallet::call_index(6)]
        #[pallet::weight(T::WeightInfo::update_network_type())]
        pub fn update_network_type(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
            network_type: NetworkType,
        ) -> DispatchResult {
            T::UpdateOrigin::ensure_origin_or_root(origin)?;
            Self::do_update_network_type(chain_id, network_type)
        }

        #[pallet::call_index(7)]
        #[pallet::weight(T::WeightInfo::update_network_gatekeeper())]
        pub fn update_network_gatekeeper(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
            gatekeeper: Vec<u8>,
        ) -> DispatchResult {
            T::UpdateOrigin::ensure_origin_or_root(origin)?;
            Self::do_update_network_gatekeeper(chain_id, gatekeeper)
        }

        #[pallet::call_index(8)]
        #[pallet::weight(T::WeightInfo::update_network_topic_name())]
        pub fn update_network_topic_name(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
            topic_name: Vec<u8>,
        ) -> DispatchResult {
            T::UpdateOrigin::ensure_origin_or_root(origin)?;
            Self::do_update_network_topic_name(chain_id, topic_name)
        }

        #[pallet::call_index(9)]
        #[pallet::weight(T::WeightInfo::update_incoming_network_fee())]
        pub fn update_incoming_network_fee(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
            incoming_fee: u32,
        ) -> DispatchResult {
            T::UpdateOrigin::ensure_origin_or_root(origin)?;
            Self::do_update_incoming_network_fee(chain_id, incoming_fee)
        }

        #[pallet::call_index(10)]
        #[pallet::weight(T::WeightInfo::update_outgoing_network_fee())]
        pub fn update_outgoing_network_fee(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
            outgoing_fee: u32,
        ) -> DispatchResult {
            T::UpdateOrigin::ensure_origin_or_root(origin)?;
            Self::do_update_outgoing_network_fee(chain_id, outgoing_fee)
        }

        #[pallet::call_index(11)]
        #[pallet::weight(T::WeightInfo::update_avg_block_speed())]
        pub fn update_avg_block_speed(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
            avg_block_speed: u64,
        ) -> DispatchResult {
            T::UpdateOrigin::ensure_origin_or_root(origin)?;
            Self::do_update_avg_block_speed(chain_id, avg_block_speed)
        }

        #[pallet::call_index(12)]
        #[pallet::weight(T::WeightInfo::remove_network())]
        pub fn remove_network(origin: OriginFor<T>, chain_id: T::NetworkId) -> DispatchResult {
            T::RemoveOrigin::ensure_origin_or_root(origin)?;
            Self::do_remove_network(chain_id)
        }
    }
}

impl<T: Config> Pallet<T> {
    /// Register a new network.
    pub fn do_register_network(chain_id: T::NetworkId, network: NetworkData) -> DispatchResult {
        Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
            ensure!(
                maybe_network.is_none(),
                Error::<T>::NetworkAlreadyRegistered
            );
            *maybe_network = Some(network.clone());
            Ok(())
        })?;

        NetworkIndexes::<T>::try_mutate(|ids| -> DispatchResult {
            ids.try_push(chain_id)
                .map_err(|_| Error::<T>::TooManyNetworks)?;
            Ok(())
        })?;

        Self::deposit_event(Event::<T>::NetworkRegistered { chain_id, network });
        Ok(())
    }

    /// Remove existent network.
    pub fn do_remove_network(chain_id: T::NetworkId) -> DispatchResult {
        NetworkIndexes::<T>::mutate(|ids| ids.retain(|id| id != &chain_id));
        Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
            ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
            *maybe_network = None;
            Ok(())
        })?;

        Self::deposit_event(Event::<T>::NetworkRemoved { chain_id });
        Ok(())
    }

    /// Update existent network name.
    pub fn do_update_network_name(chain_id: T::NetworkId, chain_name: Vec<u8>) -> DispatchResult {
        Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
            ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
            let net = maybe_network.as_mut().unwrap();
            net.chain_name = chain_name.clone();
            *maybe_network = Some(net.clone());
            Ok(())
        })?;

        Self::deposit_event(Event::<T>::NetworkNameUpdated {
            chain_id,
            chain_name,
        });
        Ok(())
    }

    /// Update existent network default endpoint.
    pub fn do_update_network_endpoint(
        chain_id: T::NetworkId,
        maybe_index: Option<u32>,
        maybe_endpoint: Option<Vec<u8>>,
    ) -> DispatchResult {
        Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
            ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
            let updated_network = maybe_network.as_mut().unwrap();
            match (maybe_index, maybe_endpoint) {
                (Some(index), Some(endpoint)) => {
                    if let Some(previous_endpoint) =
                        updated_network.default_endpoints.get_mut(index as usize)
                    {
                        *previous_endpoint = endpoint.clone();
                        Self::deposit_event(Event::<T>::NetworkEndpointUpdated {
                            chain_id,
                            index,
                            endpoint,
                        });
                    }
                }
                (None, Some(endpoint)) => {
                    updated_network.default_endpoints.push(endpoint.clone());
                    Self::deposit_event(Event::<T>::NetworkEndpointAdded { chain_id, endpoint });
                }
                (Some(index), None) => {
                    updated_network.default_endpoints.remove(index as usize);
                    Self::deposit_event(Event::<T>::NetworkEndpointRemoved { chain_id, index });
                }
                (None, None) => {}
            }
            *maybe_network = Some(updated_network.clone());
            Ok(())
        })?;
        Ok(())
    }

    /// Update existent network default finality delay.
    pub fn do_update_network_finality_delay(
        chain_id: T::NetworkId,
        finality_delay: u64,
    ) -> DispatchResult {
        Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
            ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
            let net = maybe_network.as_mut().unwrap();
            net.finality_delay = finality_delay;
            *maybe_network = Some(net.clone());
            Ok(())
        })?;
        Self::deposit_event(Event::<T>::NetworkFinalityDelayUpdated {
            chain_id,
            finality_delay,
        });
        Ok(())
    }

    /// Update existent network default rate limit delay.
    pub fn do_update_network_rate_limit_delay(
        chain_id: T::NetworkId,
        rate_limit_delay: u64,
    ) -> DispatchResult {
        Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
            ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
            let net = maybe_network.as_mut().unwrap();
            net.rate_limit_delay = rate_limit_delay;
            *maybe_network = Some(net.clone());
            Ok(())
        })?;
        Self::deposit_event(Event::<T>::NetworkRateLimitDelayUpdated {
            chain_id,
            rate_limit_delay,
        });
        Ok(())
    }

    /// Update existent network default max distance between blocks.
    pub fn do_update_network_block_distance(
        chain_id: T::NetworkId,
        block_distance: u64,
    ) -> DispatchResult {
        Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
            ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
            let net = maybe_network.as_mut().unwrap();
            net.block_distance = block_distance;
            *maybe_network = Some(net.clone());
            Ok(())
        })?;
        Self::deposit_event(Event::<T>::NetworkBlockDistanceUpdated {
            chain_id,
            block_distance,
        });
        Ok(())
    }

    /// Update existent network type.
    pub fn do_update_network_type(
        chain_id: T::NetworkId,
        network_type: NetworkType,
    ) -> DispatchResult {
        Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
            ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
            let net = maybe_network.as_mut().unwrap();
            net.network_type = network_type;
            *maybe_network = Some(net.clone());
            Ok(())
        })?;
        Self::deposit_event(Event::<T>::NetworkTypeUpdated {
            chain_id,
            network_type,
        });
        Ok(())
    }

    /// Update existent network gatekeeper.
    pub fn do_update_network_gatekeeper(
        chain_id: T::NetworkId,
        gatekeeper: Vec<u8>,
    ) -> DispatchResult {
        ensure!(
            gatekeeper.len() == 42 && gatekeeper[0] == 48 && gatekeeper[1] == 120,
            Error::<T>::WrongGatekeeperAddress
        );
        Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
            ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
            let net = maybe_network.as_mut().unwrap();
            net.gatekeeper = gatekeeper.clone();
            *maybe_network = Some(net.clone());
            Ok(())
        })?;
        Self::deposit_event(Event::<T>::NetworkGatekeeperUpdated {
            chain_id,
            gatekeeper,
        });
        Ok(())
    }

    /// Update existent network gatekeeper's topic name.
    pub fn do_update_network_topic_name(
        chain_id: T::NetworkId,
        topic_name: Vec<u8>,
    ) -> DispatchResult {
        ensure!(
            topic_name.len() == 66 && topic_name[0] == 48 && topic_name[1] == 120,
            Error::<T>::WrongTopicName
        );
        Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
            ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
            let net = maybe_network.as_mut().unwrap();
            net.topic_name = topic_name.clone();
            *maybe_network = Some(net.clone());
            Ok(())
        })?;
        Self::deposit_event(Event::<T>::NetworkTopicNameUpdated {
            chain_id,
            topic_name,
        });
        Ok(())
    }

    pub fn do_update_incoming_network_fee(
        chain_id: T::NetworkId,
        incoming_fee: u32,
    ) -> DispatchResult {
        Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
            ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
            let net = maybe_network.as_mut().unwrap();
            net.incoming_fee = incoming_fee;
            *maybe_network = Some(net.clone());
            Ok(())
        })?;
        Self::deposit_event(Event::<T>::NetworkIncomingFeeUpdated {
            chain_id,
            incoming_fee,
        });
        Ok(())
    }

    pub fn do_update_outgoing_network_fee(
        chain_id: T::NetworkId,
        outgoing_fee: u32,
    ) -> DispatchResult {
        Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
            ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
            let net = maybe_network.as_mut().unwrap();
            net.outgoing_fee = outgoing_fee;
            *maybe_network = Some(net.clone());
            Ok(())
        })?;
        Self::deposit_event(Event::<T>::NetworkOutgoingFeeUpdated {
            chain_id,
            outgoing_fee,
        });
        Ok(())
    }

    pub fn do_update_avg_block_speed(
        chain_id: T::NetworkId,
        avg_block_speed: u64,
    ) -> DispatchResult {
        Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
            ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
            let net = maybe_network.as_mut().unwrap();
            net.avg_block_speed = avg_block_speed;
            *maybe_network = Some(net.clone());
            Ok(())
        })?;
        Self::deposit_event(Event::<T>::NetworkAvgBlockSpeedUpdated {
            chain_id,
            avg_block_speed,
        });
        Ok(())
    }
}

impl<T: Config> NetworkDataBasicHandler for Pallet<T> {
    type NetworkId = T::NetworkId;
}

impl<T: Config> NetworkDataInspectHandler<NetworkData> for Pallet<T> {
    fn count() -> u32 {
        NetworkIndexes::<T>::decode_len()
            .map(|len| len as u32)
            .unwrap_or_default()
    }

    fn contains_key(n: &Self::NetworkId) -> bool {
        Networks::<T>::contains_key(n)
    }

    fn get(n: &Self::NetworkId) -> Option<NetworkData> {
        Networks::<T>::get(n)
    }

    fn network_for_block(block_number: impl Into<usize>) -> Option<(Self::NetworkId, NetworkData)> {
        let network_indexes = NetworkIndexes::<T>::get();
        block_number
            .into()
            .checked_rem(network_indexes.len())
            .map(|id| {
                network_indexes.get(id).copied().and_then(|network_id| {
                    Self::get(&network_id).map(|network_data| (network_id, network_data))
                })
            })
            .flatten()
    }

    fn iter() -> PrefixIterator<(Self::NetworkId, NetworkData)> {
        Networks::<T>::iter()
    }

    fn iter_indexes() -> impl Iterator<Item = Self::NetworkId> {
        NetworkIndexes::<T>::get().into_iter()
    }
}

impl<T: Config> NetworkDataMutateHandler<NetworkData, BalanceOf<T>> for Pallet<T> {
    fn register(chain_id: Self::NetworkId, network: NetworkData) -> DispatchResult {
        Self::do_register_network(chain_id, network)
    }

    fn remove(chain_id: Self::NetworkId) -> DispatchResult {
        Self::do_remove_network(chain_id)
    }

    fn increase_gatekeeper_amount(
        network_id: &T::NetworkId,
        amount: &BalanceOf<T>,
    ) -> Result<BalanceOf<T>, ()> {
        let new_gatekeeper_amount =
            GatekeeperAmount::<T>::mutate(network_id, |gatekeeper_amount| match gatekeeper_amount
                .checked_add(amount)
            {
                Some(value) => {
                    *gatekeeper_amount = value;
                    Ok(value)
                }
                None => Err(()),
            })?;

        Ok(new_gatekeeper_amount)
    }

    fn decrease_gatekeeper_amount(
        network_id: &T::NetworkId,
        amount: &BalanceOf<T>,
    ) -> Result<BalanceOf<T>, ()> {
        let new_gatekeeper_amount =
            GatekeeperAmount::<T>::mutate(network_id, |gatekeeper_amount| match gatekeeper_amount
                .checked_sub(amount)
            {
                Some(value) => {
                    *gatekeeper_amount = value;
                    Ok(value)
                }
                None => Err(()),
            })?;

        Ok(new_gatekeeper_amount)
    }

    fn accumulate_outgoing_imbalance(amount: &BalanceOf<T>) -> Result<BalanceOf<T>, ()> {
        let new_bridged_out_amount = BridgedImbalance::<T>::mutate(|bridged_imbalance| {
            match bridged_imbalance.bridged_out.checked_add(amount) {
                Some(value) => {
                    (*bridged_imbalance).bridged_out = value;
                    Ok(value)
                }
                None => Err(()),
            }
        })?;

        Ok(new_bridged_out_amount)
    }

    fn accumulate_incoming_imbalance(amount: &BalanceOf<T>) -> Result<BalanceOf<T>, ()> {
        let new_bridged_in_amount = BridgedImbalance::<T>::mutate(|bridged_imbalance| {
            match bridged_imbalance.bridged_in.checked_add(amount) {
                Some(value) => {
                    (*bridged_imbalance).bridged_in = value;
                    Ok(value)
                }
                None => Err(()),
            }
        })?;

        Ok(new_bridged_in_amount)
    }

    fn accumulate_commission(commission: &BalanceOf<T>) -> Result<BalanceOf<T>, ()> {
        AccumulatedCommission::<T>::mutate(|accumulated| {
            match accumulated.checked_add(commission) {
                Some(value) => {
                    *accumulated = value;
                    Ok(value)
                }
                None => Err(()),
            }
        })
    }
}
