#![cfg_attr(not(feature = "std"), no_std)]
mod benchmarking;
mod types;
pub mod weights;
pub use pallet::*;
pub use types::*;
pub use weights::WeightInfo;
use core::cmp;
#[cfg(feature = "runtime-benchmarks")]
use frame_support::traits::tokens::fungible::Mutate;
use frame_support::{
    pallet_prelude::*,
    traits::{
        fungible,
        fungible::{Credit, Inspect},
        tokens::WithdrawConsequence,
        IsSubType, StorageVersion, StoredMap,
    },
};
use frame_system::pallet_prelude::*;
use pallet_quota::traits::RefundFee;
use pallet_transaction_payment::OnChargeTransaction;
use scale_info::prelude::{
    collections::{BTreeMap, BTreeSet},
    fmt::Debug,
};
use sp_runtime::traits::{DispatchInfoOf, PostDispatchInfoOf, Saturating};
#[frame_support::pallet]
pub mod pallet {
    use super::*;
    pub type IdtyIdOf<T> = <T as pallet_identity::Config>::IdtyIndex;
    pub type CurrencyOf<T> = pallet_balances::Pallet<T>;
    type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
    pub type BalanceOf<T> = <CurrencyOf<T> as fungible::Inspect<AccountIdOf<T>>>::Balance;
    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:
        frame_system::Config<AccountData = AccountData<Self::Balance, IdtyIdOf<Self>>>
        + pallet_balances::Config
        + pallet_transaction_payment::Config
        + pallet_treasury::Config<Currency = pallet_balances::Pallet<Self>>
        + pallet_quota::Config
    {
        type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
        type WeightInfo: WeightInfo;
        type InnerOnChargeTransaction: OnChargeTransaction<Self>;
        type Refund: pallet_quota::traits::RefundFee<Self>;
    }
    #[pallet::genesis_config]
    pub struct GenesisConfig<T: Config> {
        pub accounts: BTreeMap<T::AccountId, GenesisAccountData<T::Balance, IdtyIdOf<T>>>,
        pub treasury_balance: T::Balance,
    }
    impl<T: Config> Default for GenesisConfig<T> {
        fn default() -> Self {
            Self {
                accounts: Default::default(),
                treasury_balance: T::ExistentialDeposit::get(),
            }
        }
    }
    #[pallet::genesis_build]
    impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
        fn build(&self) {
            frame_system::Account::<T>::mutate(
                pallet_treasury::Pallet::<T>::account_id(),
                |account| {
                    account.data.free = self.treasury_balance;
                    account.providers = 1;
                },
            );
            let endowed_accounts = self.accounts.keys().cloned().collect::<BTreeSet<_>>();
            assert!(
                endowed_accounts.len() == self.accounts.len(),
                "duplicate balances in genesis."
            );
            for (account_id, GenesisAccountData { balance, idty_id }) in &self.accounts {
                assert!(balance >= &T::ExistentialDeposit::get() || idty_id.is_some());
                frame_system::Account::<T>::mutate(account_id, |account| {
                    account.data.free = *balance;
                    if idty_id.is_some() {
                        account.data.linked_idty = *idty_id;
                    }
                    if balance >= &T::ExistentialDeposit::get() {
                        account.providers = 1;
                    }
                    });
            }
        }
    }
    #[pallet::event]
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
    pub enum Event<T: Config> {
        AccountLinked {
            who: T::AccountId,
            identity: IdtyIdOf<T>,
        },
        AccountUnlinked(T::AccountId),
    }
    #[pallet::call]
    impl<T: Config> Pallet<T> {
        #[pallet::call_index(0)]
        #[pallet::weight(<T as pallet::Config>::WeightInfo::unlink_identity())]
        pub fn unlink_identity(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
            let who = ensure_signed(origin)?;
            Self::do_unlink_identity(who);
            Ok(().into())
        }
    }
    impl<T: Config> Pallet<T> {
        pub fn do_unlink_identity(account_id: T::AccountId) {
            frame_system::Account::<T>::mutate(&account_id, |account| {
                if account.data.linked_idty.is_some() {
                    Self::deposit_event(Event::AccountUnlinked(account_id.clone()));
                }
                account.data.linked_idty = None;
            })
        }
        pub fn do_link_identity(account_id: &T::AccountId, idty_id: IdtyIdOf<T>) {
            if frame_system::Account::<T>::get(account_id).data.linked_idty != Some(idty_id) {
                frame_system::Account::<T>::mutate(account_id, |account| {
                    account.data.linked_idty = Some(idty_id);
                    Self::deposit_event(Event::AccountLinked {
                        who: account_id.clone(),
                        identity: idty_id,
                    });
                })
            };
        }
    }
}
impl<T> pallet_identity::traits::LinkIdty<T::AccountId, IdtyIdOf<T>> for Pallet<T>
where
    T: Config,
{
    fn link_identity(account_id: &T::AccountId, idty_id: IdtyIdOf<T>) -> Result<(), DispatchError> {
        ensure!(
            (frame_system::Account::<T>::get(account_id).providers >= 1)
                || (frame_system::Account::<T>::get(account_id).sufficients >= 1),
            pallet_identity::Error::<T>::AccountNotExist
        );
        Self::do_link_identity(account_id, idty_id);
        Ok(())
    }
}
impl<T, AccountId, Balance>
    frame_support::traits::StoredMap<AccountId, pallet_balances::AccountData<Balance>> for Pallet<T>
where
    AccountId: Parameter
        + Member
        + MaybeSerializeDeserialize
        + Debug
        + sp_runtime::traits::MaybeDisplay
        + Ord
        + Into<[u8; 32]>
        + codec::MaxEncodedLen,
    Balance: Parameter
        + Member
        + sp_runtime::traits::AtLeast32BitUnsigned
        + codec::Codec
        + Default
        + Copy
        + MaybeSerializeDeserialize
        + Debug
        + codec::MaxEncodedLen
        + scale_info::TypeInfo,
    T: Config
        + frame_system::Config<AccountId = AccountId, AccountData = AccountData<Balance, IdtyIdOf<T>>>
        + pallet_balances::Config<Balance = Balance>,
{
    fn get(k: &AccountId) -> pallet_balances::AccountData<Balance> {
        frame_system::Account::<T>::get(k).data.into()
    }
    fn try_mutate_exists<R, E: From<sp_runtime::DispatchError>>(
        account_id: &AccountId,
        f: impl FnOnce(&mut Option<pallet_balances::AccountData<Balance>>) -> Result<R, E>,
    ) -> Result<R, E> {
        let account = frame_system::Account::<T>::get(account_id);
        let was_providing = !account.data.free.is_zero() || !account.data.reserved.is_zero();
        let mut some_data = if was_providing {
            Some(account.data.into())
        } else {
            None
        };
        let result = f(&mut some_data)?;
        let is_providing = some_data.is_some();
        match (was_providing, is_providing) {
            (false, true) => {
                frame_system::Pallet::<T>::inc_providers(account_id);
            }
            (true, false) => {
                match frame_system::Pallet::<T>::dec_providers(account_id)? {
                    frame_system::DecRefStatus::Reaped => return Ok(result),
                    frame_system::DecRefStatus::Exists => {
                        }
                }
            }
            (false, false) => {
                return Ok(result);
            }
            (true, true) => {
                }
        }
        frame_system::Account::<T>::mutate(account_id, |a| {
            a.data.set_balances(some_data.unwrap_or_default())
        });
        Ok(result)
    }
}
impl<T: Config> OnChargeTransaction<T> for Pallet<T>
where
    T::RuntimeCall: IsSubType<Call<T>>,
    T::InnerOnChargeTransaction: OnChargeTransaction<
        T,
        Balance = BalanceOf<T>,
        LiquidityInfo = Option<Credit<T::AccountId, T::Currency>>,
    >,
{
    type Balance = BalanceOf<T>;
    type LiquidityInfo = Option<Credit<T::AccountId, T::Currency>>;
    fn withdraw_fee(
        who: &T::AccountId,
        call: &T::RuntimeCall,
        dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
        fee: Self::Balance,
        tip: Self::Balance,
    ) -> Result<Self::LiquidityInfo, TransactionValidityError> {
        T::InnerOnChargeTransaction::withdraw_fee(who, call, dispatch_info, fee, tip)
    }
    fn correct_and_deposit_fee(
        who: &T::AccountId,
        dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
        post_info: &PostDispatchInfoOf<T::RuntimeCall>,
        corrected_fee: Self::Balance,
        tip: Self::Balance,
        already_withdrawn: Self::LiquidityInfo,
    ) -> Result<(), TransactionValidityError> {
        T::InnerOnChargeTransaction::correct_and_deposit_fee(
            who,
            dispatch_info,
            post_info,
            corrected_fee,
            tip,
            already_withdrawn,
        )?;
        let account_data = frame_system::Pallet::<T>::get(who);
        if let Some(idty_index) = account_data.linked_idty {
            T::Refund::request_refund(who.clone(), idty_index, corrected_fee.saturating_sub(tip));
        }
        Ok(())
    }
}
impl<AccountId, T: Config> pallet_identity::traits::CheckAccountWorthiness<T> for Pallet<T>
where
    T: frame_system::Config<AccountId = AccountId>,
    AccountId: cmp::Eq,
{
    fn check_account_worthiness(account: &AccountId) -> Result<(), DispatchError> {
        ensure!(
            frame_system::Pallet::<T>::providers(account) > 0,
            pallet_identity::Error::<T>::AccountNotExist
        );
        ensure!(
            T::Currency::can_withdraw(account, T::Currency::minimum_balance() * 2u32.into())
                == WithdrawConsequence::Success,
            pallet_identity::Error::<T>::InsufficientBalance
        );
        Ok(())
    }
    #[cfg(feature = "runtime-benchmarks")]
    fn set_worthy(account: &AccountId) {
        T::Currency::set_balance(account, T::Currency::minimum_balance() * 4u32.into());
    }
}