use super::*;
use frame_support::{dispatch::DispatchResultWithPostInfo, traits::PrivilegeCmp};
use ghost_traits::exposure::ExposureListener;
use pallet_alliance::{ProposalIndex, ProposalProvider};
use primitives::Balance;
use sp_runtime::DispatchError;
use sp_std::{cmp::Ordering, marker::PhantomData};

type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
type ProposalOf<T, I> = <T as pallet_collective::Config<I>>::Proposal;
type HashOf<T> = <T as frame_system::Config>::Hash;

/// Proposal provider for alliance pallet.
/// Adapter from collective pallet to alliance proposal provider trait.
pub struct AllianceProposalProvider<T, I = ()>(PhantomData<(T, I)>);
impl<T, I> ProposalProvider<AccountIdOf<T>, HashOf<T>, ProposalOf<T, I>>
    for AllianceProposalProvider<T, I>
where
    T: pallet_collective::Config<I> + frame_system::Config,
    I: 'static,
{
    fn propose_proposal(
        who: AccountIdOf<T>,
        threshold: u32,
        proposal: Box<ProposalOf<T, I>>,
        length_bound: u32,
    ) -> Result<(u32, u32), DispatchError> {
        pallet_collective::Pallet::<T, I>::do_propose_proposed(
            who,
            threshold,
            proposal,
            length_bound,
        )
    }

    fn vote_proposal(
        who: AccountIdOf<T>,
        proposal: HashOf<T>,
        index: ProposalIndex,
        approve: bool,
    ) -> Result<bool, DispatchError> {
        pallet_collective::Pallet::<T, I>::do_vote(who, proposal, index, approve)
    }

    fn close_proposal(
        proposal_hash: HashOf<T>,
        proposal_index: ProposalIndex,
        proposal_weight_bound: Weight,
        length_bound: u32,
    ) -> DispatchResultWithPostInfo {
        pallet_collective::Pallet::<T, I>::do_close(
            proposal_hash,
            proposal_index,
            proposal_weight_bound,
            length_bound,
        )
    }

    fn proposal_of(proposal_hash: HashOf<T>) -> Option<ProposalOf<T, I>> {
        pallet_collective::ProposalOf::<T, I>::get(proposal_hash)
    }
}

/// Used the compare the privilege of an origin inside the scheduler.
pub struct EqualOrGreatestRootCmp;
impl PrivilegeCmp<OriginCaller> for EqualOrGreatestRootCmp {
    fn cmp_privilege(left: &OriginCaller, right: &OriginCaller) -> Option<Ordering> {
        if left == right {
            return Some(Ordering::Equal);
        }

        match (left, right) {
            // Root is greater than anything
            (OriginCaller::system(frame_system::RawOrigin::Root), _) => Some(Ordering::Greater),
            // For every other we don't care, as they are not used for `ScheduleOrigin`.
            _ => None,
        }
    }
}

/// Used to get the exposure information out of staking pallet directly.
pub struct StakingExposureListener<T>(PhantomData<T>);
impl<T> ExposureListener<Balance, AccountIdOf<T>> for StakingExposureListener<T>
where
    T: pallet_session::Config + pallet_staking::Config + frame_system::Config,
    u128: From<T::CurrencyBalance>,
    AccountIdOf<T>: From<<T as pallet_session::Config>::ValidatorId>,
{
    fn get_account_by_index(index: usize) -> Option<AccountIdOf<T>> {
        pallet_session::Pallet::<T>::validators()
            .get(index)
            .map(|validator| {
                let account_id: AccountIdOf<T> = validator.clone().into();
                account_id
            })
    }

    fn get_total_exposure() -> Balance {
        let era = pallet_staking::Pallet::<T>::current_era().unwrap_or_default();
        pallet_staking::Pallet::<T>::eras_total_stake(era).into()
    }

    fn get_validator_exposure(validator: &AccountIdOf<T>) -> Balance {
        let era = pallet_staking::Pallet::<T>::current_era().unwrap_or_default();
        pallet_staking::EraInfo::<T>::get_full_exposure(era, validator)
            .total
            .into()
    }
}

#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarks {
    use super::*;
    use frame_support::traits::fungible;
    use pallet_ranked_collective::Rank;
    use sp_runtime::traits::Convert;

    /// Rank to salary conversion helper type
    pub struct RankToSalary<Fungible>(PhantomData<Fungible>);
    impl<Fungible> Convert<Rank, Balance> for RankToSalary<Fungible>
    where
        Fungible: fungible::Inspect<AccountId, Balance = Balance>,
    {
        fn convert(r: Rank) -> Balance {
            Balance::from(r).saturating_mul(Fungible::minimum_balance())
        }
    }
}
