#![cfg_attr(not(feature = "std"), no_std)]
extern crate core;
mod impls;
mod manager;
mod traits;
#[cfg(test)]
mod tests;
use frame_support::{pallet_prelude::Get, traits::StorageVersion};
pub use manager::SessionAndEraManager;
pub use pallet::*;
use parity_scale_codec::{Decode, Encode};
use core_primitives::{BanConfig as BanConfigStruct, BanInfo, SessionValidators, LENIENT_THRESHOLD};
use scale_info::TypeInfo;
use sp_runtime::Perquintill;
use sp_std::{collections::btree_map::BTreeMap, default::Default};
pub use traits::*;
pub type TotalReward = u32;
#[derive(Decode, Encode, TypeInfo, PartialEq, Eq)]
pub struct ValidatorTotalRewards<T>(pub BTreeMap<T, TotalReward>);
#[derive(Decode, Encode, TypeInfo)]
struct CurrentAndNextSessionValidators<T> {
    pub next: SessionValidators<T>,
    pub current: SessionValidators<T>,
}
impl<T> Default for CurrentAndNextSessionValidators<T> {
    fn default() -> Self {
        Self {
            next: Default::default(),
            current: Default::default(),
        }
    }
}
pub struct DefaultLenientThreshold;
impl Get<Perquintill> for DefaultLenientThreshold {
    fn get() -> Perquintill {
        LENIENT_THRESHOLD
    }
}
const STORAGE_VERSION: StorageVersion = StorageVersion::new(0);
pub(crate) const LOG_TARGET: &str = "pallet-committee-management";
#[frame_support::pallet]
pub mod pallet {
    use frame_support::{
        dispatch::DispatchResult, ensure, pallet_prelude::*, BoundedVec, Twox64Concat,
    };
    use frame_system::{ensure_root, pallet_prelude::OriginFor};
    use core_primitives::{
        BanHandler, BanReason, BlockCount, FinalityCommitteeManager, SessionCount,
        SessionValidators, ValidatorProvider,
    };
    use sp_runtime::{Perbill, Perquintill};
    use sp_staking::EraIndex;
    use sp_std::vec::Vec;
    use crate::{
        traits::{EraInfoProvider, ValidatorRewardsHandler},
        BanConfigStruct, BanInfo, CurrentAndNextSessionValidators, DefaultLenientThreshold,
        ValidatorExtractor, ValidatorTotalRewards, STORAGE_VERSION,
    };
    #[pallet::config]
    pub trait Config: frame_system::Config {
        type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
        type BanHandler: BanHandler<AccountId = Self::AccountId>;
        type EraInfoProvider: EraInfoProvider<AccountId = Self::AccountId>;
        type ValidatorProvider: ValidatorProvider<AccountId = Self::AccountId>;
        type ValidatorRewardsHandler: ValidatorRewardsHandler<AccountId = Self::AccountId>;
        type ValidatorExtractor: ValidatorExtractor<AccountId = Self::AccountId>;
        type FinalityCommitteeManager: FinalityCommitteeManager<Self::AccountId>;
        #[pallet::constant]
        type SessionPeriod: Get<u32>;
    }
    #[pallet::pallet]
    #[pallet::storage_version(STORAGE_VERSION)]
    #[pallet::without_storage_info]
    pub struct Pallet<T>(_);
    #[pallet::storage]
    pub type LenientThreshold<T: Config> =
    StorageValue<_, Perquintill, ValueQuery, DefaultLenientThreshold>;
    #[pallet::storage]
    pub type SessionValidatorBlockCount<T: Config> =
    StorageMap<_, Twox64Concat, T::AccountId, BlockCount, ValueQuery>;
    #[pallet::storage]
    pub type ValidatorEraTotalReward<T: Config> =
    StorageValue<_, ValidatorTotalRewards<T::AccountId>, OptionQuery>;
    #[pallet::storage]
    pub type BanConfig<T> = StorageValue<_, BanConfigStruct, ValueQuery>;
    #[pallet::storage]
    pub type UnderperformedValidatorSessionCount<T: Config> =
    StorageMap<_, Twox64Concat, T::AccountId, SessionCount, ValueQuery>;
    #[pallet::storage]
    pub type Banned<T: Config> = StorageMap<_, Twox64Concat, T::AccountId, BanInfo>;
    #[pallet::storage]
    pub(crate) type CurrentAndNextSessionValidatorsStorage<T: Config> =
    StorageValue<_, CurrentAndNextSessionValidators<T::AccountId>, ValueQuery>;
    #[pallet::error]
    pub enum Error<T> {
        InvalidBanConfig,
        BanReasonTooBig,
        InvalidLenientThreshold,
    }
    #[pallet::event]
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
    pub enum Event<T: Config> {
        SetBanConfig(BanConfigStruct),
        BanValidators(Vec<(T::AccountId, BanInfo)>),
        ValidatorBanned {
            banned: T::AccountId,
            ban_info: BanInfo,
        },
        ValidatorUnbanned(T::AccountId),
        NewLenientThreshold(u8),
    }
    #[pallet::call]
    impl<T: Config> Pallet<T> {
        #[pallet::call_index(1)]
        #[pallet::weight((T::BlockWeights::get().max_block, DispatchClass::Operational))]
        pub fn set_ban_config(
            origin: OriginFor<T>,
            minimal_expected_performance: Option<u8>,
            underperformed_session_count_threshold: Option<u32>,
            clean_session_counter_delay: Option<u32>,
            ban_period: Option<EraIndex>,
        ) -> DispatchResult {
            ensure_root(origin)?;
            let mut current_committee_ban_config = BanConfig::<T>::get();
            if let Some(minimal_expected_performance) = minimal_expected_performance {
                ensure!(
                    minimal_expected_performance <= 100,
                    Error::<T>::InvalidBanConfig
                );
                current_committee_ban_config.minimal_expected_performance =
                    Perbill::from_percent(minimal_expected_performance as u32);
            }
            if let Some(underperformed_session_count_threshold) =
                underperformed_session_count_threshold
            {
                ensure!(
                    underperformed_session_count_threshold > 0,
                    Error::<T>::InvalidBanConfig
                );
                current_committee_ban_config.underperformed_session_count_threshold =
                    underperformed_session_count_threshold;
            }
            if let Some(clean_session_counter_delay) = clean_session_counter_delay {
                ensure!(
                    clean_session_counter_delay > 0,
                    Error::<T>::InvalidBanConfig
                );
                current_committee_ban_config.clean_session_counter_delay =
                    clean_session_counter_delay;
            }
            if let Some(ban_period) = ban_period {
                ensure!(ban_period > 0, Error::<T>::InvalidBanConfig);
                current_committee_ban_config.ban_period = ban_period;
            }
            BanConfig::<T>::put(current_committee_ban_config.clone());
            Self::deposit_event(Event::SetBanConfig(current_committee_ban_config));
            Ok(())
        }
        #[pallet::call_index(2)]
        #[pallet::weight((T::BlockWeights::get().max_block, DispatchClass::Operational))]
        pub fn ban_from_committee(
            origin: OriginFor<T>,
            banned: T::AccountId,
            ban_reason: Vec<u8>,
        ) -> DispatchResult {
            ensure_root(origin)?;
            let bounded_description: BoundedVec<_, _> = ban_reason
                .try_into()
                .map_err(|_| Error::<T>::BanReasonTooBig)?;
            let reason = BanReason::OtherReason(bounded_description);
            Self::ban_validator(&banned, reason);
            let ban_info = Banned::<T>::get(&banned).unwrap();
            Self::deposit_event(Event::<T>::ValidatorBanned { banned, ban_info });
            Ok(())
        }
        #[pallet::call_index(3)]
        #[pallet::weight((T::BlockWeights::get().max_block, DispatchClass::Operational))]
        pub fn cancel_ban(origin: OriginFor<T>, banned: T::AccountId) -> DispatchResult {
            ensure_root(origin)?;
            Banned::<T>::remove(banned.clone());
            Self::deposit_event(Event::ValidatorUnbanned(banned));
            Ok(())
        }
        #[pallet::call_index(4)]
        #[pallet::weight((T::BlockWeights::get().max_block, DispatchClass::Operational))]
        pub fn set_lenient_threshold(
            origin: OriginFor<T>,
            threshold_percent: u8,
        ) -> DispatchResult {
            ensure_root(origin)?;
            ensure!(
                threshold_percent <= 100,
                Error::<T>::InvalidLenientThreshold
            );
            LenientThreshold::<T>::put(Perquintill::from_percent(threshold_percent as u64));
            Ok(())
        }
    }
    #[pallet::genesis_config]
    #[derive(frame_support::DefaultNoBound)]
    pub struct GenesisConfig<T: Config> {
        pub committee_ban_config: BanConfigStruct,
        pub session_validators: SessionValidators<T::AccountId>,
    }
    #[pallet::genesis_build]
    impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
        fn build(&self) {
            <BanConfig<T>>::put(self.committee_ban_config.clone());
            <CurrentAndNextSessionValidatorsStorage<T>>::put(CurrentAndNextSessionValidators {
                current: self.session_validators.clone(),
                next: self.session_validators.clone(),
            })
        }
    }
}