#[cfg(not(feature = "tanssi"))]
use pezframe_support::traits::EitherOfDiverse;
use pezframe_support::{parameter_types, traits::InstanceFilter, weights::Weight, PezpalletId};
#[cfg(not(feature = "tanssi"))]
use pezframe_system::EnsureRoot;
#[cfg(not(feature = "tanssi"))]
use pezpallet_xcm::{EnsureXcm, IsVoiceOfBody};
use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
use pezkuwi_runtime_common::impls::{
    LocatableAssetConverter, VersionedLocatableAsset, VersionedLocationConverter,
};
use scale_info::TypeInfo;
use pezsp_core::ConstU32;
use pezsp_runtime::{
    generic,
    traits::{BlakeTwo256, IdentifyAccount, Verify},
    MultiAddress, MultiSignature, Perbill, RuntimeDebug,
};
use pezsp_version::RuntimeVersion;
use xcm::{
    latest::{InteriorLocation, Junction::PezpalletInstance},
    VersionedLocation,
};
use xcm_builder::PayOverXcm;

use crate::{
    configs::XcmRouter,
    constants::{HOURS, VERSION},
    Treasury,
};
#[cfg(not(feature = "tanssi"))]
use crate::{
    configs::{xcm_config::RelayLocation, StakingAdminBodyId},
    constants::{
        BLOCK_PTYRESSING_VELOCITY, RELAY_CHAIN_SLOT_DURATION_MILLIS, UNINCLUDED_SEGMENT_CAPACITY,
    },
};
pub use crate::{
    configs::{FeeAssetId, ToSiblingBaseDeliveryFee, TransactionByteFee},
    AllPezpalletsWithSystem, Runtime, RuntimeBlockWeights, RuntimeCall, XcmpQueue,
};

/// Alias to 512-bit hash when used in the context of a transaction signature on
/// the chain.
pub type Signature = MultiSignature;

/// Some way of identifying an account on the chain. We intentionally make it
/// equivalent to the public key of our transaction signing scheme.
pub type AccountId = <<Signature as Verify>::Signer as IdentifyAccount>::AccountId;

/// Balance of an account.
pub type Balance = u128;

/// Identifier of an asset
pub type AssetId = u32;

/// Index of a transaction in the chain.
pub type Nonce = u32;

/// A hash of some data used by the chain.
pub type Hash = pezsp_core::H256;

/// An index to a block.
pub type BlockNumber = u32;

/// The address format for describing accounts.
pub type Address = MultiAddress<AccountId, ()>;

/// Block header type as expected by this runtime.
pub type Header = generic::Header<BlockNumber, BlakeTwo256>;

/// Block type as expected by this runtime.
pub type Block = generic::Block<Header, UncheckedExtrinsic>;

/// The TransactionExtension to the basic transaction logic.
pub type TxExtension = pezcumulus_pezpallet_weight_reclaim::StorageWeightReclaim<
    Runtime,
    (
        pezframe_system::CheckNonZeroSender<Runtime>,
        pezframe_system::CheckSpecVersion<Runtime>,
        pezframe_system::CheckTxVersion<Runtime>,
        pezframe_system::CheckGenesis<Runtime>,
        pezframe_system::CheckEra<Runtime>,
        pezframe_system::CheckNonce<Runtime>,
        pezframe_system::CheckWeight<Runtime>,
        pezpallet_asset_tx_payment::ChargeAssetTxPayment<Runtime>,
        pezframe_metadata_hash_extension::CheckMetadataHash<Runtime>,
    ),
>;

/// Unchecked extrinsic type as expected by this runtime.
pub type UncheckedExtrinsic =
    generic::UncheckedExtrinsic<Address, RuntimeCall, Signature, TxExtension>;

/// Executive: handles dispatch to the various modules.
pub type Executive = pezframe_executive::Executive<
    Runtime,
    Block,
    pezframe_system::ChainContext<Runtime>,
    Runtime,
    AllPezpalletsWithSystem,
>;

/// Price For Sibling TeyrChain Delivery
pub type PriceForSiblingTeyrChainDelivery = pezkuwi_runtime_common::xcm_sender::ExponentialPrice<
    FeeAssetId,
    ToSiblingBaseDeliveryFee,
    TransactionByteFee,
    XcmpQueue,
>;

/// Configures the number of blocks that can be created without submission of validity proof to the relay chain
#[cfg(not(feature = "tanssi"))]
pub type ConsensusHook = pezcumulus_pezpallet_aura_ext::FixedVelocityConsensusHook<
    Runtime,
    RELAY_CHAIN_SLOT_DURATION_MILLIS,
    BLOCK_PTYRESSING_VELOCITY,
    UNINCLUDED_SEGMENT_CAPACITY,
>;

/// We allow root and the StakingAdmin to execute privileged collator selection
/// operations.
#[cfg(not(feature = "tanssi"))]
pub type CollatorSelectionUpdateOrigin = EitherOfDiverse<
    EnsureRoot<AccountId>,
    EnsureXcm<IsVoiceOfBody<RelayLocation, StakingAdminBodyId>>,
>;

/// These aliases are describing the Beneficiary and AssetKind for the Treasury pezpallet
pub type Beneficiary = VersionedLocation;
pub type AssetKind = VersionedLocatableAsset;

/// This is a type that describes how we should transfer bounties from treasury pezpallet
pub type TreasuryPaymaster = PayOverXcm<
    TreasuryInteriorLocation,
    XcmRouter,
    crate::PezkuwiXcm,
    ConstU32<{ 6 * HOURS }>,
    Beneficiary,
    AssetKind,
    LocatableAssetConverter,
    VersionedLocationConverter,
>;

/// The type used to represent the kinds of proxying allowed.
/// If you are adding new pezpallets, consider adding new ProxyType variant
#[derive(
    Copy,
    Clone,
    Decode,
    DecodeWithMemTracking,
    Default,
    Encode,
    Eq,
    MaxEncodedLen,
    Ord,
    PartialEq,
    PartialOrd,
    RuntimeDebug,
    TypeInfo,
)]
pub enum ProxyType {
    /// Allows to proxy all calls
    #[default]
    Any,
    /// Allows all non-transfer calls
    NonTransfer,
    /// Allows to finish the proxy
    CancelProxy,
    #[cfg(not(feature = "tanssi"))]
    /// Allows to operate with collators list (invulnerables, candidates, etc.)
    Collator,
}

impl InstanceFilter<RuntimeCall> for ProxyType {
    fn filter(&self, c: &RuntimeCall) -> bool {
        match self {
            ProxyType::Any => true,
            ProxyType::NonTransfer => !matches!(c, RuntimeCall::Balances { .. }),
            ProxyType::CancelProxy => matches!(
                c,
                RuntimeCall::Proxy(pezpallet_proxy::Call::reject_announcement { .. })
                    | RuntimeCall::Multisig { .. }
            ),
            #[cfg(not(feature = "tanssi"))]
            ProxyType::Collator => {
                matches!(c, RuntimeCall::CollatorSelection { .. } | RuntimeCall::Multisig { .. })
            }
        }
    }
}

// Getter types used in OpenZeppelinRuntime configuration
parameter_types! {
    pub const Version: RuntimeVersion = VERSION;
    pub const TreasuryPezpalletId: PezpalletId = PezpalletId(*b"py/trsry");
    pub TreasuryAccount: AccountId = Treasury::account_id();
    // The asset's interior location for the paying account. This is the Treasury
    // pezpallet instance (which sits at index 13).
    pub TreasuryInteriorLocation: InteriorLocation = PezpalletInstance(13).into();
    pub MessageQueueServiceWeight: Weight = Perbill::from_percent(35) * RuntimeBlockWeights::get().max_block;
}

#[cfg(test)]
mod test {
    mod filter {
        use pezframe_support::traits::InstanceFilter;
        use pezsp_core::H256;

        use crate::{types::ProxyType, AssetManager, RuntimeCall};

        #[test]
        #[cfg(not(feature = "tanssi"))]
        fn test_filter_any() {
            let call = RuntimeCall::CollatorSelection(
                pezpallet_collator_selection::Call::set_desired_candidates { max: 10 },
            );
            let proxy_type = ProxyType::Any;
            assert!(proxy_type.filter(&call));
        }

        #[test]
        #[cfg(not(feature = "tanssi"))]
        fn test_filter_nontransfer() {
            let proxy_type = ProxyType::NonTransfer;
            let valid_call = RuntimeCall::CollatorSelection(
                pezpallet_collator_selection::Call::set_desired_candidates { max: 10 },
            );
            assert!(proxy_type.filter(&valid_call));
            let invalid_call =
                RuntimeCall::Balances(pezpallet_balances::Call::burn { value: 1, keep_alive: true });
            assert!(!proxy_type.filter(&invalid_call));
        }

        #[test]
        #[cfg(not(feature = "tanssi"))]
        fn test_filter_cancel_proxy() {
            let proxy_type = ProxyType::CancelProxy;
            let invalid_call = RuntimeCall::CollatorSelection(
                pezpallet_collator_selection::Call::set_desired_candidates { max: 10 },
            );
            assert!(!proxy_type.filter(&invalid_call));
            let valid_call = RuntimeCall::Proxy(pezpallet_proxy::Call::reject_announcement {
                delegate: pezsp_runtime::MultiAddress::Id(AssetManager::account_id()),
                call_hash: H256::zero(),
            });
            assert!(proxy_type.filter(&valid_call));
        }

        #[test]
        #[cfg(not(feature = "tanssi"))]
        fn test_filter_collator() {
            let proxy_type = ProxyType::Collator;
            let valid_call = RuntimeCall::CollatorSelection(
                pezpallet_collator_selection::Call::set_desired_candidates { max: 10 },
            );
            assert!(proxy_type.filter(&valid_call));
            let invalid_call =
                RuntimeCall::Balances(pezpallet_balances::Call::burn { value: 1, keep_alive: true });
            assert!(!proxy_type.filter(&invalid_call));
        }

        #[test]
        fn test_filter_default() {
            let expected = ProxyType::Any;
            assert_eq!(expected, ProxyType::default());
        }
    }
}
