#[cfg(feature = "try-runtime")]
use frame_support::ensure;
use frame_support::{
	pallet_prelude::GetStorageVersion,
	storage::unhashed,
	traits::{Get, OnRuntimeUpgrade, PalletInfoAccess, StorageVersion},
	weights::{RuntimeDbWeight, Weight},
};
use scale_info::prelude::format;
use sp_io::MultiRemovalResults;
#[cfg(feature = "try-runtime")]
use sp_runtime::DispatchError;
#[cfg(feature = "try-runtime")]
use sp_std::vec::Vec;
pub struct KillPallet<PalletName, DbWeight>(sp_std::marker::PhantomData<(PalletName, DbWeight)>);
impl<PalletName, DbWeight> OnRuntimeUpgrade for KillPallet<PalletName, DbWeight>
where
	PalletName: Get<&'static str>,
	DbWeight: Get<RuntimeDbWeight>,
{
	#[cfg(feature = "try-runtime")]
	fn pre_upgrade() -> Result<Vec<u8>, DispatchError> {
		if !unhashed::contains_prefixed_key(&sp_io::hashing::twox_128(PalletName::get().as_bytes()))
		{
			log::info!(
				"Clear pallet {:?}: Pallet prefix doesn't exist, storage is empty already",
				PalletName::get(),
			)
		}
		Ok(Vec::new())
	}
	fn on_runtime_upgrade() -> Weight {
		log::info!(
			"Clear pallet {:?}: nuking pallet prefix...",
			PalletName::get()
		);
		let result = unhashed::clear_prefix(
			&sp_io::hashing::twox_128(PalletName::get().as_bytes()),
			None,
			None,
		);
		match result.maybe_cursor {
			None => log::info!(
				"Clear pallet {:?}: storage cleared successful",
				PalletName::get()
			),
			Some(_) => {
				log::error!(
					"Clear pallet {:?}: storage not totally cleared",
					PalletName::get()
				)
			}
		}
		log::info!(
			"Clear pallet {:?}: iteration result. backend: {} unique: {} loops: {}",
			PalletName::get(),
			result.backend,
			result.unique,
			result.loops,
		);
		DbWeight::get().writes(result.unique.into()) + DbWeight::get().reads(result.loops.into())
	}
	#[cfg(feature = "try-runtime")]
	fn post_upgrade(_: Vec<u8>) -> Result<(), DispatchError> {
		ensure!(
			!unhashed::contains_prefixed_key(&sp_io::hashing::twox_128(
				PalletName::get().as_bytes()
			)),
			"Pallet prefix still exists!"
		);
		Ok(())
	}
}
pub struct ResetPallet<Pallet, DbWeight, const ON_CHAIN_VERSION: u16>(
	sp_std::marker::PhantomData<(Pallet, DbWeight)>,
);
impl<Pallet, DbWeight, const ON_CHAIN_VERSION: u16> OnRuntimeUpgrade
	for ResetPallet<Pallet, DbWeight, ON_CHAIN_VERSION>
where
	Pallet: GetStorageVersion<CurrentStorageVersion = StorageVersion> + PalletInfoAccess,
	DbWeight: Get<RuntimeDbWeight>,
{
	#[cfg(feature = "try-runtime")]
	fn pre_upgrade() -> Result<Vec<u8>, DispatchError> {
		ensure!(
			Pallet::on_chain_storage_version() == StorageVersion::new(ON_CHAIN_VERSION),
			"Pallet on-chain version must match with ON_CHAIN_VERSION"
		);
		ensure!(
			Pallet::on_chain_storage_version() < Pallet::current_storage_version(),
			"Pallet is already updated"
		);
		if !unhashed::contains_prefixed_key(&pallet_prefix::<Pallet>()) {
			log::info!(
				"Nuke-{}: Pallet prefix doesn't exist, storage is empty already",
				Pallet::name(),
			)
		}
		Ok(Vec::new())
	}
	fn on_runtime_upgrade() -> Weight {
		if Pallet::on_chain_storage_version() != StorageVersion::new(ON_CHAIN_VERSION) {
			log::error!(
				"Nuke-{}: nuke aborted. This upgrade must be removed!",
				Pallet::name()
			);
			return Weight::zero();
		}
		if Pallet::on_chain_storage_version() < Pallet::current_storage_version() {
			log::info!("Nuke-{}: nuking pallet...", Pallet::name());
			let result = unhashed::clear_prefix(&pallet_prefix::<Pallet>(), None, None);
			storage_clean_res_log(&result, "", &format!("Nuke-{}", Pallet::name()));
			Pallet::current_storage_version().put::<Pallet>();
			DbWeight::get().writes(result.unique.into())
				+ DbWeight::get().reads(result.loops.into())
				+ DbWeight::get().reads_writes(1, 1) } else {
			log::warn!(
				"Nuke-{}: pallet on-chain version is not less than {:?}. This upgrade can be removed.",
				Pallet::name(),
				Pallet::current_storage_version()
			);
			DbWeight::get().reads(1)
		}
	}
	#[cfg(feature = "try-runtime")]
	fn post_upgrade(_: Vec<u8>) -> Result<(), DispatchError> {
		assert_eq!(
			Pallet::on_chain_storage_version(),
			Pallet::current_storage_version(),
			"on-chain storage version should have been updated"
		);
		ensure!(
			!contains_prefixed_key_skip_storage_version::<Pallet>(&pallet_prefix::<Pallet>()),
			"Pallet prefix still exists!"
		);
		Ok(())
	}
}
fn pallet_prefix<Pallet: PalletInfoAccess>() -> [u8; 16] {
	sp_io::hashing::twox_128(Pallet::name().as_bytes())
}
pub fn contains_prefixed_key_skip_storage_version<Pallet: PalletInfoAccess>(prefix: &[u8]) -> bool {
	let mut next_key = prefix.to_vec();
	loop {
		match sp_io::storage::next_key(&next_key) {
			Some(key) if key == StorageVersion::storage_key::<Pallet>() => next_key = key,
			Some(key) => break key.starts_with(prefix),
			None => {
				break false;
			}
		}
	}
}
pub fn storage_clean_res_log(res: &MultiRemovalResults, storage_prefix: &str, log_prefix: &str) {
	match res.maybe_cursor {
		None => log::info!("{log_prefix}: Cleared all {storage_prefix} storage entries"),
		Some(_) => {
			log::error!("{log_prefix}: {storage_prefix} storage not totally cleared",)
		}
	}
	log::info!(
		"{log_prefix} Clear result of {storage_prefix}: backend: {} unique: {} loops: {}",
		res.backend,
		res.unique,
		res.loops,
	);
}