use cfg_primitives::types::{AccountId, Balance};
use cfg_types::tokens::{CrossChainTransferability, CurrencyId, CustomMetadata};
use frame_support::traits::{fungibles::Mutate, Everything, Get};
use frame_system::pallet_prelude::BlockNumberFor;
use orml_traits::asset_registry::Inspect;
use polkadot_parachain_primitives::primitives::Sibling;
use sp_runtime::traits::{AccountIdConversion, Convert, MaybeEquivalence, Zero};
use sp_std::marker::PhantomData;
use staging_xcm::v4::{
	Asset, AssetId,
	Fungibility::Fungible,
	Junction::{AccountId32, GeneralKey, Parachain},
	Location, NetworkId,
};
use staging_xcm_builder::{
	AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom,
	AllowTopLevelPaidExecutionFrom, DescribeAllTerminal, DescribeFamily, HashedDescription,
	ParentIsPreset, SiblingParachainConvertsVia, SignedToAccountId32, TakeRevenue,
	TakeWeightCredit,
};
use crate::xcm_fees::{default_per_second, native_per_second};
pub struct FixedConversionRateProvider<OrmlAssetRegistry>(PhantomData<OrmlAssetRegistry>);
impl<
		OrmlAssetRegistry: orml_traits::asset_registry::Inspect<
			AssetId = CurrencyId,
			Balance = Balance,
			CustomMetadata = CustomMetadata,
		>,
	> orml_traits::FixedConversionRateProvider for FixedConversionRateProvider<OrmlAssetRegistry>
{
	fn get_fee_per_second(location: &Location) -> Option<u128> {
		let metadata = OrmlAssetRegistry::metadata_by_location(location)?;
		match metadata.additional.transferability {
			CrossChainTransferability::Xcm(xcm_metadata) => xcm_metadata
				.fee_per_second
				.or_else(|| Some(default_per_second(metadata.decimals))),
			_ => None,
		}
	}
}
pub fn general_key(data: &[u8]) -> staging_xcm::latest::Junction {
	GeneralKey {
		length: data.len().min(32) as u8,
		data: cfg_utils::vec_to_fixed_array(data),
	}
}
frame_support::parameter_types! {
	pub CanonicalNativePerSecond: (AssetId, u128, u128) = (
		Location::new(
			0,
			general_key(cfg_primitives::NATIVE_KEY),
		).into(),
		native_per_second(),
		0,
	);
}
pub struct AccountIdToLocation;
impl<AccountId: Into<[u8; 32]>> Convert<AccountId, Location> for AccountIdToLocation {
	fn convert(account: AccountId) -> Location {
		AccountId32 {
			network: None,
			id: account.into(),
		}
		.into()
	}
}
pub type LocalOriginToLocation<R> = SignedToAccountId32<
	<R as frame_system::Config>::RuntimeOrigin,
	AccountId,
	NetworkIdByGenesis<R>,
>;
pub struct NetworkIdByGenesis<T>(sp_std::marker::PhantomData<T>);
impl<T: frame_system::Config> Get<Option<NetworkId>> for NetworkIdByGenesis<T>
where
	<T as frame_system::Config>::Hash: Into<[u8; 32]>,
{
	fn get() -> Option<NetworkId> {
		Some(NetworkId::ByGenesis(
			frame_system::BlockHash::<T>::get(BlockNumberFor::<T>::zero()).into(),
		))
	}
}
pub struct CurrencyIdConvert<T>(PhantomData<T>);
impl<T> MaybeEquivalence<Location, CurrencyId> for CurrencyIdConvert<T>
where
	T: orml_asset_registry::module::Config<AssetId = CurrencyId, CustomMetadata = CustomMetadata>
		+ staging_parachain_info::Config,
{
	fn convert(location: &Location) -> Option<CurrencyId> {
		let para_id = staging_parachain_info::Pallet::<T>::parachain_id();
		let unanchored_location = match location {
			Location {
				parents: 0,
				interior,
			} => Location {
				parents: 1,
				interior: interior
					.clone()
					.pushed_front_with(Parachain(u32::from(para_id)))
					.ok()?,
			},
			x => x.clone(),
		};
		orml_asset_registry::module::Pallet::<T>::asset_id(&unanchored_location)
	}
	fn convert_back(id: &CurrencyId) -> Option<Location> {
		orml_asset_registry::module::Pallet::<T>::metadata(id)
			.filter(|m| m.additional.transferability.includes_xcm())
			.and_then(|m| m.location)
			.and_then(|l| l.try_into().ok())
	}
}
impl<T> Convert<CurrencyId, Option<Location>> for CurrencyIdConvert<T>
where
	T: orml_asset_registry::module::Config<AssetId = CurrencyId, CustomMetadata = CustomMetadata>
		+ staging_parachain_info::Config,
{
	fn convert(id: CurrencyId) -> Option<Location> {
		<Self as MaybeEquivalence<_, _>>::convert_back(&id)
	}
}
impl<T> Convert<Location, Option<CurrencyId>> for CurrencyIdConvert<T>
where
	T: orml_asset_registry::module::Config<AssetId = CurrencyId, CustomMetadata = CustomMetadata>
		+ staging_parachain_info::Config,
{
	fn convert(location: Location) -> Option<CurrencyId> {
		<Self as MaybeEquivalence<_, _>>::convert(&location)
	}
}
pub struct ToTreasury<T>(PhantomData<T>);
impl<T> TakeRevenue for ToTreasury<T>
where
	T: orml_asset_registry::module::Config<AssetId = CurrencyId, CustomMetadata = CustomMetadata>
		+ staging_parachain_info::Config
		+ pallet_restricted_tokens::Config<CurrencyId = CurrencyId, Balance = Balance>,
{
	fn take_revenue(revenue: Asset) {
		if let Asset {
			id: AssetId(location),
			fun: Fungible(amount),
		} = revenue
		{
			if let Some(currency_id) =
				<CurrencyIdConvert<T> as MaybeEquivalence<_, _>>::convert(&location)
			{
				let treasury_account = cfg_types::ids::TREASURY_PALLET_ID.into_account_truncating();
				let _ = pallet_restricted_tokens::Pallet::<T>::mint_into(
					currency_id,
					&treasury_account,
					amount,
				);
			}
		}
	}
}
pub type Barrier<PolkadotXcm> = (
	TakeWeightCredit,
	AllowTopLevelPaidExecutionFrom<Everything>,
	AllowKnownQueryResponses<PolkadotXcm>,
	AllowSubscriptionsFrom<Everything>,
);
pub type LocationToAccountId<RelayNetwork> = (
	ParentIsPreset<AccountId>,
	SiblingParachainConvertsVia<Sibling, AccountId>,
	AccountId32Aliases<RelayNetwork, AccountId>,
	HashedDescription<AccountId, DescribeFamily<DescribeAllTerminal>>,
);