// Copyright 2022 Webb Technologies Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! # DKG Metadata Module
//!
//! A pallet to manage metadata about the DKG and DKG proposal system.
//!
//! ## Overview
//!
//! The DKG Metadata pallet manages the following metadata about the DKG and DKG proposal system:
//! - The active DKG public key
//! - The active DKG authorities
//! - The next DKG public key
//! - The next DKG authorities
//! - The DKG signature threshold (the `t` in `t-out-of-n` threshold signatures)
//! - The key refresh process
//! - The misbehavior reporting process
//!
//! The pallet tracks authority changes after each session and updates the metadata for the
//! current and next authority sets respectively. The pallet also exposes a way for the root origin
//! to update the signature threshold used in the DKG signing system.
//!
//! The pallet is responsible with initiating the key refresh process for rotating the DKG keys
//! across authority set changes and executing the rotation when a new session starts.
//!
//! The pallet tracks reputations of DKG authorities by providing extrinsics and storage for
//! submitting misbehaviour reports about authorities that misbehave.
//!
//! ### Terminology
//!
//! - **Authority**: A DKG authority.
//! - **Authority Set**: A set of DKG authorities.
//! - **Active DKG public key**: The public key of the DKG protocol that is currently active.
//! - **Next DKG public key**: The public key of the DKG protocol that will be active after the next
//!   authority set change.
//! - **DKG signature threshold**: The `t` in `t-out-of-n` threshold signatures used in the DKG
//!   signing system.
//! - **Misbheaviour Report**: A report of a DKG authority misbehaving.
//! - **Refresh delay**: The length of time within a session that the key refresh protocol will be
//!   initiated.
//!
//! ### Implementation
//!
//! The pallet is implemented to track and maintain a variety of different pieces of data. It is the
//! primary pallet used to store the active and next DKG public keys that are generated by the
//! active and next authorities of the underlying blockchain protocol. A runtime API is used to
//! expose the active and next authorities to the external workers responsible with executing the
//! DKG and signing protocol.
//!
//! The pallet exposes a variety of extrinsics for updating metadata:
//! - `set_threshold`: Allows a root-origin to update the DKG signature threshold used in the
//!   threshold signing protocol.
//! - `set_refresh_delay`: Allows a root-origin to update the delay before the key refresh process
//!   is initiated.
//! - `submit_misbehaviour_reports`: Allows authorities to submit misbehaviour reports. Once the
//!   `threshold` number of reports is submitted, the offending authority will lose reputation.
//! - `submit_public_key`: Allows submitting of the genesis public key by the initial authorities of
//!   the DKG protocol.
//! - `submit_next_public_key`: Allows submitting of the next public key by the next authorities of
//!   the DKG protocol.
//! - `submit_public_key_signature`: Allows submitting of the signature of the next public key by
//!   the active DKG key for eventual rotation.
//!
//! The refresh process is initiated `T::RefreshDelay` into each session. A RefreshProposal is
//! created and sent directly to the `pallet-dkg-proposal-handler` for inclusion into the unsigned
//! proposal queue and soon after for signing. Once this proposal is signed, the signature is posted
//! back on-chain to this pallet which rotates the DKG keys when the session ends.
//!
//! The misbehaviour reporting process relies on a honest-threshold assumption. When DKG authorities
//! misbehave offchain, any detecting authority can submit a report of the offending authority. If a
//! `threshold` number of reports is submitted, then the offending authority will lose reputation.
//! This reputation map is utilized by each DKG authority to ensure every authority can generate a
//! deterministic signing set for the threshold signing protocols. The signing set is taken to
//! initially be the top `t` DKG authorities by reputation.
//!
//! ## Related Modules
//!
//! * [`System`](https://github.com/paritytech/substrate/tree/master/frame/system)
//! * [`Support`](https://github.com/paritytech/substrate/tree/master/frame/support)
//! * [`DKG Proposals`](../../pallet-dkg-proposals)
//! * [`DKG Proposal Handler`](../../pallet-dkg-proposal-handler)

#![cfg_attr(not(feature = "std"), no_std)]

use codec::Encode;

use frame_support::{
	dispatch::DispatchResultWithPostInfo,
	traits::{EstimateNextSessionRotation, Get, OneSessionHandler},
	Parameter,
};
use frame_system::offchain::{SendSignedTransaction, Signer};

use dkg_runtime_primitives::{
	offchain::storage_keys::{
		AGGREGATED_MISBEHAVIOUR_REPORTS, AGGREGATED_MISBEHAVIOUR_REPORTS_LOCK,
		AGGREGATED_PUBLIC_KEYS, AGGREGATED_PUBLIC_KEYS_AT_GENESIS,
		AGGREGATED_PUBLIC_KEYS_AT_GENESIS_LOCK, AGGREGATED_PUBLIC_KEYS_LOCK,
		OFFCHAIN_PUBLIC_KEY_SIG, OFFCHAIN_PUBLIC_KEY_SIG_LOCK, SUBMIT_GENESIS_KEYS_AT,
		SUBMIT_KEYS_AT,
	},
	traits::{GetDKGPublicKey, OnAuthoritySetChangeHandler},
	utils::{sr25519, to_slice_32, verify_signer_from_set},
	AggregatedMisbehaviourReports, AggregatedPublicKeys, AuthorityIndex, AuthoritySet,
	ConsensusLog, RefreshProposal, RefreshProposalSigned, DKG_ENGINE_ID,
};
use sp_runtime::{
	generic::DigestItem,
	offchain::{
		storage::StorageValueRef,
		storage_lock::{StorageLock, Time},
	},
	traits::{IsMember, Member},
	DispatchError, Permill, RuntimeAppPublic,
};
use sp_std::{collections::btree_map::BTreeMap, prelude::*};

pub mod types;
use types::RoundMetadata;

#[cfg(test)]
mod mock;

#[cfg(test)]
mod tests;

pub use pallet::*;

#[frame_support::pallet]
pub mod pallet {
	use super::*;
	use dkg_runtime_primitives::{traits::OnDKGPublicKeyChangeHandler, ProposalHandlerTrait};
	use frame_support::{ensure, pallet_prelude::*, transactional};
	use frame_system::{
		ensure_signed,
		offchain::{AppCrypto, CreateSignedTransaction},
		pallet_prelude::*,
	};
	use sp_runtime::Permill;

	#[pallet::config]
	pub trait Config: frame_system::Config + CreateSignedTransaction<Call<Self>> {
		/// The overarching event type.
		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
		/// Authority identifier type
		type DKGId: Member + Parameter + RuntimeAppPublic + MaybeSerializeDeserialize;

		/// The identifier type for an offchain worker.
		type OffChainAuthId: AppCrypto<Self::Public, Self::Signature>;

		/// Listener for authority set changes
		type OnAuthoritySetChangeHandler: OnAuthoritySetChangeHandler<
			Self::AccountId,
			dkg_runtime_primitives::AuthoritySetId,
			Self::DKGId,
		>;

		type OnDKGPublicKeyChangeHandler: OnDKGPublicKeyChangeHandler<
			dkg_runtime_primitives::AuthoritySetId,
		>;

		type ProposalHandler: ProposalHandlerTrait;

		/// A type that gives allows the pallet access to the session progress
		type NextSessionRotation: EstimateNextSessionRotation<Self::BlockNumber>;

		/// Percentage session should have progressed for refresh to begin
		#[pallet::constant]
		type RefreshDelay: Get<Permill>;
		/// Default number of blocks after which dkg keygen will be restarted if it has stalled
		#[pallet::constant]
		type TimeToRestart: Get<Self::BlockNumber>;
	}

	#[pallet::pallet]
	#[pallet::generate_store(pub(super) trait Store)]
	#[pallet::without_storage_info]
	pub struct Pallet<T>(PhantomData<T>);

	#[pallet::hooks]
	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
		fn offchain_worker(block_number: T::BlockNumber) {
			let _res = Self::submit_genesis_public_key_onchain(block_number);
			let _res = Self::submit_next_public_key_onchain(block_number);
			let _res = Self::submit_public_key_signature_onchain(block_number);
			let _res = Self::submit_misbehaviour_reports_onchain(block_number);
			let (authority_id, pk) = DKGPublicKey::<T>::get();
			let maybe_next_key = NextDKGPublicKey::<T>::get();
			#[cfg(feature = "std")] // required since we use hex and strings
			frame_support::log::debug!(
				target: "dkg",
				"Current Authority({}) DKG PublicKey:
				**********************************************************
				compressed: 0x{}
				uncompressed: 0x{}
				**********************************************************",
				authority_id,
				hex::encode(pk.clone()),
				hex::encode(Self::decompress_public_key(pk).unwrap_or_default()),
			);
			#[cfg(feature = "std")] // required since we use hex and strings
			if let Some((next_authority_id, next_pk)) = maybe_next_key {
				frame_support::log::debug!(
					target: "dkg",
					"Next Authority({}) DKG PublicKey:
					**********************************************************
					compressed: 0x{}
					uncompressed: 0x{}
					**********************************************************",
					next_authority_id,
					hex::encode(next_pk.clone()),
					hex::encode(Self::decompress_public_key(next_pk).unwrap_or_default()),
				);
			}
		}

		fn on_initialize(n: BlockNumberFor<T>) -> frame_support::weights::Weight {
			if Self::should_refresh(n) && !Self::refresh_in_progress() {
				if let Some(pub_key) = Self::next_dkg_public_key() {
					RefreshInProgress::<T>::put(true);
					let uncompressed_pub_key =
						Self::decompress_public_key(pub_key.1).unwrap_or_default();
					let next_nonce = Self::refresh_nonce() + 1u32;
					let data = dkg_runtime_primitives::RefreshProposal {
						nonce: next_nonce.into(),
						pub_key: uncompressed_pub_key,
					};
					match T::ProposalHandler::handle_unsigned_refresh_proposal(data) {
						Ok(()) => {
							RefreshNonce::<T>::put(next_nonce);
							frame_support::log::debug!("Handled refresh proposal");
						},
						Err(e) => {
							frame_support::log::warn!("Failed to handle refresh proposal: {:?}", e);
						},
					}
				}
			}

			0
		}
	}

	#[pallet::call]
	impl<T: Config> Pallet<T> {
		/// Set the pending signature threshold for the session following the next session.
		///
		/// We cannot assume that the next DKG has not already completed keygen.
		/// After all, if we are in a new session the next DKG may have already completed.
		/// Therefore, when we update the thresholds we are updating a threshold
		/// that will become the next threshold after the next session update.
		#[pallet::weight(0)]
		pub fn set_signature_threshold(
			origin: OriginFor<T>,
			new_threshold: u16,
		) -> DispatchResultWithPostInfo {
			ensure_root(origin)?;
			ensure!(
				usize::from(new_threshold) < NextAuthorities::<T>::get().len(),
				Error::<T>::InvalidThreshold
			);
			PendingSignatureThreshold::<T>::try_mutate(|threshold| {
				*threshold = new_threshold;
				Ok(().into())
			})
		}

		/// Set the pending keygen threshold for the session following the next session.
		///
		/// We cannot assume that the next DKG has not already completed keygen.
		/// After all, if we are in a new session the next DKG may have already completed.
		/// Therefore, when we update the thresholds we are updating a threshold
		/// that will become the next threshold after the next session update.
		#[transactional]
		#[pallet::weight(0)]
		pub fn set_keygen_threshold(
			origin: OriginFor<T>,
			new_threshold: u16,
		) -> DispatchResultWithPostInfo {
			ensure_root(origin)?;
			ensure!(
				usize::from(new_threshold) <= NextAuthorities::<T>::get().len(),
				Error::<T>::InvalidThreshold
			);

			if new_threshold <= PendingSignatureThreshold::<T>::get() {
				Self::update_signature_threshold(new_threshold.saturating_sub(1))?;
			}

			Self::update_keygen_threshold(new_threshold)
		}

		#[pallet::weight(0)]
		pub fn set_refresh_delay(
			origin: OriginFor<T>,
			new_delay: u8,
		) -> DispatchResultWithPostInfo {
			ensure_root(origin)?;

			ensure!(new_delay <= 100, Error::<T>::InvalidRefreshDelay);

			// set the new delay
			RefreshDelay::<T>::put(Permill::from_percent(new_delay as u32));

			Ok(().into())
		}

		#[transactional]
		#[pallet::weight(0)]
		pub fn submit_public_key(
			origin: OriginFor<T>,
			keys_and_signatures: AggregatedPublicKeys,
		) -> DispatchResultWithPostInfo {
			let origin = ensure_signed(origin)?;
			ensure!(!DKGPublicKey::<T>::exists(), Error::<T>::AlreadySubmittedPublicKey);

			let authorities = Self::current_authorities_accounts();
			ensure!(authorities.contains(&origin), Error::<T>::MustBeAnActiveAuthority);
			let dict = Self::process_public_key_submissions(keys_and_signatures, authorities);
			let threshold = Self::next_signature_threshold();

			let mut accepted = false;
			for (key, accounts) in dict.iter() {
				if accounts.len() >= (threshold + 1) as usize {
					DKGPublicKey::<T>::put((Self::authority_set_id(), key.clone()));
					Self::deposit_event(Event::PublicKeySubmitted {
						compressed_pub_key: key.clone(),
						uncompressed_pub_key: Self::decompress_public_key(key.clone())
							.unwrap_or_default(),
					});
					accepted = true;

					break
				}
			}

			if accepted {
				return Ok(().into())
			}

			Err(Error::<T>::InvalidPublicKeys.into())
		}

		#[transactional]
		#[pallet::weight(0)]
		pub fn submit_next_public_key(
			origin: OriginFor<T>,
			keys_and_signatures: AggregatedPublicKeys,
		) -> DispatchResultWithPostInfo {
			let origin = ensure_signed(origin)?;
			ensure!(!NextDKGPublicKey::<T>::exists(), Error::<T>::AlreadySubmittedPublicKey);

			let next_authorities = Self::next_authorities_accounts();
			ensure!(next_authorities.contains(&origin), Error::<T>::MustBeAQueuedAuthority);
			let dict = Self::process_public_key_submissions(keys_and_signatures, next_authorities);
			let threshold = Self::next_signature_threshold();

			let mut accepted = false;
			for (key, accounts) in dict.iter() {
				if accounts.len() >= (threshold + 1) as usize {
					NextDKGPublicKey::<T>::put((Self::authority_set_id() + 1u64, key.clone()));
					Self::deposit_event(Event::NextPublicKeySubmitted {
						compressed_pub_key: key.clone(),
						uncompressed_pub_key: Self::decompress_public_key(key.clone())
							.unwrap_or_default(),
					});
					accepted = true;

					break
				}
			}

			if accepted {
				// TODO Do something about accounts that posted a wrong key
				return Ok(().into())
			}

			Err(Error::<T>::InvalidPublicKeys.into())
		}

		#[transactional]
		#[pallet::weight(0)]
		pub fn submit_public_key_signature(
			origin: OriginFor<T>,
			signature_proposal: RefreshProposalSigned,
		) -> DispatchResultWithPostInfo {
			ensure_signed(origin)?;
			ensure!(Self::next_dkg_public_key().is_some(), Error::<T>::NoNextPublicKey);
			ensure!(
				Self::next_public_key_signature().is_none(),
				Error::<T>::AlreadySubmittedSignature
			);
			let used_signatures = Self::used_signatures();
			// Deconstruct the signature and nonce for easier access
			let (nonce, signature) = (signature_proposal.nonce, signature_proposal.signature);
			ensure!(
				nonce == Self::refresh_nonce().into() || !signature.is_empty(),
				Error::<T>::InvalidSignature
			);
			ensure!(!used_signatures.contains(&signature), Error::<T>::UsedSignature);

			let (_, next_pub_key) = Self::next_dkg_public_key().unwrap();
			let data = RefreshProposal {
				nonce: Self::refresh_nonce().into(),
				pub_key: Self::decompress_public_key(next_pub_key).unwrap_or_default(),
			};
			dkg_runtime_primitives::utils::ensure_signed_by_dkg::<Self>(&signature, &data.encode())
				.map_err(|_| {
					#[cfg(feature = "std")]
					frame_support::log::error!(
						target: "dkg",
						"Invalid signature for RefreshProposal
						**********************************************************
						signature: {:?}
						**********************************************************",
						hex::encode(signature.clone()),
					);
					Error::<T>::InvalidSignature
				})?;

			NextPublicKeySignature::<T>::put(signature.clone());
			// Remove unsigned refresh proposal from queue
			T::ProposalHandler::handle_signed_refresh_proposal(data)?;
			Self::deposit_event(Event::NextPublicKeySignatureSubmitted { pub_key_sig: signature });
			// Handle manual refresh if flag is set
			if Self::should_manual_refresh() {
				ShouldManualRefresh::<T>::put(false);
				let next_authority_set_id = Self::authority_set_id() + 1u64;
				AuthoritySetId::<T>::put(next_authority_set_id);
				Self::refresh_keys();
				Self::deposit_event(Event::RefreshKeysFinished { next_authority_set_id });
			}

			Ok(().into())
		}

		#[transactional]
		#[pallet::weight(0)]
		pub fn submit_misbehaviour_reports(
			origin: OriginFor<T>,
			reports: AggregatedMisbehaviourReports,
		) -> DispatchResultWithPostInfo {
			let origin = ensure_signed(origin)?;

			let authorities = Self::current_authorities_accounts();
			ensure!(authorities.contains(&origin), Error::<T>::MustBeAnActiveAuthority);
			let offender = reports.offender.clone();
			let valid_reporters = Self::process_misbehaviour_reports(reports, authorities);
			let threshold = Self::signature_threshold();

			if valid_reporters.len() >= (threshold + 1) as usize {
				// Deduct one point for misbehaviour report
				let reputation = AuthorityReputations::<T>::get(&offender);
				AuthorityReputations::<T>::insert(&offender, reputation - 1);
				Self::deposit_event(Event::MisbehaviourReportsSubmitted {
					reporters: valid_reporters,
				});
				return Ok(().into())
			}

			Err(Error::<T>::InvalidMisbehaviourReports.into())
		}

		#[pallet::weight(0)]
		pub fn set_time_to_restart(
			origin: OriginFor<T>,
			interval: T::BlockNumber,
		) -> DispatchResultWithPostInfo {
			ensure_root(origin)?;
			TimeToRestart::<T>::put(interval);
			Ok(().into())
		}

		/// Manually Update the `RefreshNonce` (increment it by one).
		///
		/// * `origin` - The account that is calling this must be root.
		/// **Important**: This function is only available for testing purposes.
		#[pallet::weight(0)]
		#[transactional]
		pub fn manual_increment_nonce(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
			ensure_root(origin)?;
			if Self::refresh_in_progress() {
				return Err(Error::<T>::RefreshInProgress.into())
			}
			let next_nonce = Self::refresh_nonce() + 1u32;
			RefreshNonce::<T>::put(next_nonce);
			Ok(().into())
		}

		/// Manual Trigger DKG Refresh process.
		///
		/// * `origin` - The account that is initiating the refresh process and must be root.
		/// **Important**: This function is only available for testing purposes.
		#[pallet::weight(0)]
		#[transactional]
		pub fn manual_refresh(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
			ensure_root(origin)?;
			if Self::refresh_in_progress() {
				return Err(Error::<T>::RefreshInProgress.into())
			}
			if let Some(pub_key) = Self::next_dkg_public_key() {
				RefreshInProgress::<T>::put(true);
				let uncompressed_pub_key = Self::decompress_public_key(pub_key.1).unwrap();
				let next_nonce = Self::refresh_nonce() + 1u32;
				let data = dkg_runtime_primitives::RefreshProposal {
					nonce: next_nonce.into(),
					pub_key: uncompressed_pub_key,
				};

				match T::ProposalHandler::handle_unsigned_refresh_proposal(data) {
					Ok(()) => {
						RefreshNonce::<T>::put(next_nonce);
						ShouldManualRefresh::<T>::put(true);
						frame_support::log::debug!("Handled refresh proposal");
						Ok(().into())
					},
					Err(e) => {
						frame_support::log::warn!("Failed to handle refresh proposal: {:?}", e);
						Err(Error::<T>::ManualRefreshFailed.into())
					},
				}
			} else {
				Err(Error::<T>::NoNextPublicKey.into())
			}
		}
	}

	/// Public key Signatures for past sessions
	#[pallet::storage]
	#[pallet::getter(fn used_signatures)]
	pub type UsedSignatures<T: Config> = StorageValue<_, Vec<Vec<u8>>, ValueQuery>;

	/// Nonce value for next refresh proposal
	#[pallet::storage]
	#[pallet::getter(fn refresh_nonce)]
	pub type RefreshNonce<T: Config> = StorageValue<_, u32, ValueQuery>;

	/// Session progress required to kickstart refresh process
	#[pallet::storage]
	#[pallet::getter(fn refresh_delay)]
	pub type RefreshDelay<T: Config> = StorageValue<_, Permill, ValueQuery>;

	/// Check if there is a refresh in progress.
	#[pallet::storage]
	#[pallet::getter(fn refresh_in_progress)]
	pub type RefreshInProgress<T: Config> = StorageValue<_, bool, ValueQuery>;

	/// Should we manually trigger a DKG refresh process.
	#[pallet::storage]
	#[pallet::getter(fn should_manual_refresh)]
	pub type ShouldManualRefresh<T: Config> = StorageValue<_, bool, ValueQuery>;

	/// Number of blocks that should elapse after which the dkg keygen is restarted
	/// if it has stalled
	#[pallet::storage]
	#[pallet::getter(fn time_to_restart)]
	pub type TimeToRestart<T: Config> = StorageValue<_, T::BlockNumber, ValueQuery>;

	/// Holds public key for next session
	#[pallet::storage]
	#[pallet::getter(fn next_dkg_public_key)]
	pub type NextDKGPublicKey<T: Config> =
		StorageValue<_, (dkg_runtime_primitives::AuthoritySetId, Vec<u8>), OptionQuery>;

	/// Signature of the DKG public key for the next session
	#[pallet::storage]
	#[pallet::getter(fn next_public_key_signature)]
	pub type NextPublicKeySignature<T: Config> = StorageValue<_, Vec<u8>, OptionQuery>;

	/// Holds active public key for ongoing session
	#[pallet::storage]
	#[pallet::getter(fn dkg_public_key)]
	pub type DKGPublicKey<T: Config> =
		StorageValue<_, (dkg_runtime_primitives::AuthoritySetId, Vec<u8>), ValueQuery>;

	/// Signature of the current DKG public key
	#[pallet::storage]
	#[pallet::getter(fn public_key_signature)]
	pub type DKGPublicKeySignature<T: Config> = StorageValue<_, Vec<u8>, ValueQuery>;

	/// Holds public key for immediate past session
	#[pallet::storage]
	#[pallet::getter(fn previous_public_key)]
	pub type PreviousPublicKey<T: Config> =
		StorageValue<_, (dkg_runtime_primitives::AuthoritySetId, Vec<u8>), ValueQuery>;

	/// Tracks current proposer set
	#[pallet::storage]
	#[pallet::getter(fn historical_rounds)]
	pub type HistoricalRounds<T: Config> = StorageMap<
		_,
		Blake2_256,
		dkg_runtime_primitives::AuthoritySetId,
		RoundMetadata,
		ValueQuery,
	>;

	/// The current signature threshold (i.e. the `t` in t-of-n)
	#[pallet::storage]
	#[pallet::getter(fn signature_threshold)]
	pub(super) type SignatureThreshold<T: Config> = StorageValue<_, u16, ValueQuery>;

	/// The current signature threshold (i.e. the `n` in t-of-n)
	#[pallet::storage]
	#[pallet::getter(fn keygen_threshold)]
	pub(super) type KeygenThreshold<T: Config> = StorageValue<_, u16, ValueQuery>;

	/// The current signature threshold (i.e. the `t` in t-of-n)
	#[pallet::storage]
	#[pallet::getter(fn next_signature_threshold)]
	pub(super) type NextSignatureThreshold<T: Config> = StorageValue<_, u16, ValueQuery>;

	/// The current signature threshold (i.e. the `n` in t-of-n)
	#[pallet::storage]
	#[pallet::getter(fn next_keygen_threshold)]
	pub(super) type NextKeygenThreshold<T: Config> = StorageValue<_, u16, ValueQuery>;

	/// The pending signature threshold (i.e. the `t` in t-of-n)
	#[pallet::storage]
	#[pallet::getter(fn pending_signature_threshold)]
	pub(super) type PendingSignatureThreshold<T: Config> = StorageValue<_, u16, ValueQuery>;

	/// The pending signature threshold (i.e. the `n` in t-of-n)
	#[pallet::storage]
	#[pallet::getter(fn pending_keygen_threshold)]
	pub(super) type PendingKeygenThreshold<T: Config> = StorageValue<_, u16, ValueQuery>;

	/// The current authorities set
	#[pallet::storage]
	#[pallet::getter(fn authorities)]
	pub(super) type Authorities<T: Config> = StorageValue<_, Vec<T::DKGId>, ValueQuery>;

	/// The current authority set id
	#[pallet::storage]
	#[pallet::getter(fn authority_set_id)]
	pub(super) type AuthoritySetId<T: Config> =
		StorageValue<_, dkg_runtime_primitives::AuthoritySetId, ValueQuery>;

	/// Authorities set scheduled to be used with the next session
	#[pallet::storage]
	#[pallet::getter(fn next_authorities)]
	pub(super) type NextAuthorities<T: Config> = StorageValue<_, Vec<T::DKGId>, ValueQuery>;

	/// Accounts for the current authorities
	#[pallet::storage]
	#[pallet::getter(fn current_authorities_accounts)]
	pub(super) type CurrentAuthoritiesAccounts<T: Config> =
		StorageValue<_, Vec<T::AccountId>, ValueQuery>;

	/// Authority account ids scheduled for the next session
	#[pallet::storage]
	#[pallet::getter(fn next_authorities_accounts)]
	pub(super) type NextAuthoritiesAccounts<T: Config> =
		StorageValue<_, Vec<T::AccountId>, ValueQuery>;

	/// Tracks misbehaviour reports
	#[pallet::storage]
	#[pallet::getter(fn misbehaviour_reports)]
	pub type MisbehaviourReports<T: Config> = StorageMap<
		_,
		Blake2_256,
		(dkg_runtime_primitives::AuthoritySetId, dkg_runtime_primitives::crypto::AuthorityId),
		AggregatedMisbehaviourReports,
		OptionQuery,
	>;

	/// Tracks authority reputations
	#[pallet::storage]
	#[pallet::getter(fn authority_reputations)]
	pub type AuthorityReputations<T: Config> =
		StorageMap<_, Blake2_256, dkg_runtime_primitives::crypto::AuthorityId, i64, ValueQuery>;

	#[pallet::genesis_config]
	pub struct GenesisConfig<T: Config> {
		pub authorities: Vec<T::DKGId>,
		pub threshold: u32,
		pub authority_ids: Vec<T::AccountId>,
	}

	#[pallet::error]
	pub enum Error<T> {
		/// Invalid threshold
		InvalidThreshold,
		/// Must be queued  to become an authority
		MustBeAQueuedAuthority,
		/// Must be an an authority
		MustBeAnActiveAuthority,
		/// Refresh delay should be in the range of 0% - 100%
		InvalidRefreshDelay,
		/// Invalid public key submission
		InvalidPublicKeys,
		/// Already submitted a public key
		AlreadySubmittedPublicKey,
		/// Already submitted a public key signature
		AlreadySubmittedSignature,
		/// Used signature from past sessions
		UsedSignature,
		/// Invalid public key signature submission
		InvalidSignature,
		/// Invalid misbehaviour reports
		InvalidMisbehaviourReports,
		/// DKG Refresh is already in progress.
		RefreshInProgress,
		/// Manual DKG Refresh failed to progress.
		ManualRefreshFailed,
		/// No NextPublicKey stored on-chain.
		NoNextPublicKey,
	}

	// Pallets use events to inform users when important changes are made.
	#[pallet::event]
	#[pallet::generate_deposit(pub fn deposit_event)]
	pub enum Event<T: Config> {
		/// Current public key submitted
		PublicKeySubmitted { compressed_pub_key: Vec<u8>, uncompressed_pub_key: Vec<u8> },
		/// Next public key submitted
		NextPublicKeySubmitted { compressed_pub_key: Vec<u8>, uncompressed_pub_key: Vec<u8> },
		/// Next public key signature submitted
		NextPublicKeySignatureSubmitted { pub_key_sig: Vec<u8> },
		/// Current Public Key Changed.
		PublicKeyChanged { compressed_pub_key: Vec<u8>, uncompressed_pub_key: Vec<u8> },
		/// Current Public Key Signature Changed.
		PublicKeySignatureChanged { pub_key_sig: Vec<u8> },
		/// Misbehaviour reports submitted
		MisbehaviourReportsSubmitted { reporters: Vec<sr25519::Public> },
		/// Refresh DKG Keys Finished (forcefully).
		RefreshKeysFinished { next_authority_set_id: dkg_runtime_primitives::AuthoritySetId },
	}

	#[cfg(feature = "std")]
	impl<T: Config> Default for GenesisConfig<T> {
		fn default() -> Self {
			Self { authorities: Vec::new(), threshold: 0, authority_ids: Vec::new() }
		}
	}

	#[pallet::genesis_build]
	impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {
		fn build(&self) {
			let mut signature_threshold = u16::try_from(self.authorities.len() / 2).unwrap() + 1;
			let keygen_threshold = u16::try_from(self.authorities.len()).unwrap();

			if keygen_threshold <= signature_threshold {
				signature_threshold = keygen_threshold - 1;
			}

			// Set thresholds to be the same
			SignatureThreshold::<T>::put(signature_threshold);
			KeygenThreshold::<T>::put(keygen_threshold);
			NextSignatureThreshold::<T>::put(signature_threshold);
			NextKeygenThreshold::<T>::put(keygen_threshold);
			PendingSignatureThreshold::<T>::put(signature_threshold);
			PendingKeygenThreshold::<T>::put(keygen_threshold);
			// Set refresh parameters
			RefreshDelay::<T>::put(T::RefreshDelay::get());
			RefreshNonce::<T>::put(0);
			TimeToRestart::<T>::put(T::TimeToRestart::get());
		}
	}
}

impl<T: Config> Pallet<T> {
	/// Return the current active DKG authority set.
	pub fn authority_set() -> AuthoritySet<T::DKGId> {
		AuthoritySet::<T::DKGId> { authorities: Self::authorities(), id: Self::authority_set_id() }
	}

	pub fn update_signature_threshold(new_threshold: u16) -> DispatchResultWithPostInfo {
		PendingSignatureThreshold::<T>::try_mutate(|threshold| {
			*threshold = new_threshold;
			Ok(().into())
		})
	}

	pub fn update_keygen_threshold(new_threshold: u16) -> DispatchResultWithPostInfo {
		PendingKeygenThreshold::<T>::try_mutate(|threshold| {
			*threshold = new_threshold;
			Ok(().into())
		})
	}

	pub fn decompress_public_key(compressed: Vec<u8>) -> Result<Vec<u8>, DispatchError> {
		let result = libsecp256k1::PublicKey::parse_slice(
			&compressed,
			Some(libsecp256k1::PublicKeyFormat::Compressed),
		)
		.map(|pk| pk.serialize())
		.map_err(|_| Error::<T>::InvalidPublicKeys)?;
		if result.len() == 65 {
			// remove the 0x04 prefix
			Ok(result[1..].to_vec())
		} else {
			Ok(result.to_vec())
		}
	}

	pub fn process_public_key_submissions(
		aggregated_keys: AggregatedPublicKeys,
		authorities: Vec<T::AccountId>,
	) -> BTreeMap<Vec<u8>, Vec<Vec<u8>>> {
		let mut dict: BTreeMap<Vec<u8>, Vec<Vec<u8>>> = BTreeMap::new();

		for (pub_key, signature) in aggregated_keys.keys_and_signatures {
			let maybe_signers = authorities
				.iter()
				.map(|x| {
					sr25519::Public(to_slice_32(&x.encode()).unwrap_or_else(|| {
						panic!("Failed to convert account id to sr25519 public key")
					}))
				})
				.collect::<Vec<sr25519::Public>>();

			let can_proceed = verify_signer_from_set(maybe_signers, &pub_key, &signature);

			if can_proceed.1 {
				if !dict.contains_key(&pub_key) {
					dict.insert(pub_key.clone(), Vec::new());
				}
				let temp = dict.get_mut(&pub_key).unwrap();
				temp.push(can_proceed.0.unwrap().encode());
			}
		}

		dict
	}

	pub fn process_misbehaviour_reports(
		reports: AggregatedMisbehaviourReports,
		authorities: Vec<T::AccountId>,
	) -> Vec<sr25519::Public> {
		let mut valid_reporters = Vec::new();
		for (inx, signature) in reports.signatures.iter().enumerate() {
			let maybe_signers = authorities
				.iter()
				.map(|x| {
					sr25519::Public(to_slice_32(&x.encode()).unwrap_or_else(|| {
						panic!("Failed to convert account id to sr25519 public key")
					}))
				})
				.collect::<Vec<sr25519::Public>>();

			let mut signed_payload = Vec::new();
			signed_payload.extend_from_slice(reports.round_id.to_be_bytes().as_ref());
			signed_payload.extend_from_slice(reports.offender.as_ref());

			let can_proceed = verify_signer_from_set(maybe_signers, &signed_payload, signature);

			if can_proceed.1 && !valid_reporters.contains(&reports.reporters[inx]) {
				valid_reporters.push(reports.reporters[inx]);
			}
		}

		valid_reporters
	}

	/// Change the current DKG authority set by rotating in the `new_authority_ids` set.
	///
	/// This function is meant to be called on a new session when the next authorities
	/// become the current or `new` authorities. We track the accounts for these
	/// authorities as well.
	fn change_authorities(
		new_authority_ids: Vec<T::DKGId>,
		next_authority_ids: Vec<T::DKGId>,
		new_authorities_accounts: Vec<T::AccountId>,
		next_authorities_accounts: Vec<T::AccountId>,
	) {
		let next_id = Self::authority_set_id() + 1u64;
		// Ensure pending thresholds remain valid across authority set changes that may break.
		// We update the pending thresholds because we call `refresh_keys` below, which rotates
		// all the thresholds into the current / next sets. Pending becomes the next, next becomes
		// the current.
		if next_authority_ids.len() < Self::pending_keygen_threshold().into() {
			PendingKeygenThreshold::<T>::put(next_authority_ids.len() as u16);
		}
		if next_authority_ids.len() <= Self::pending_signature_threshold().into() {
			PendingSignatureThreshold::<T>::put(next_authority_ids.len() as u16 - 1);
		}
		// Update the new and next authorities
		Authorities::<T>::put(&new_authority_ids);
		CurrentAuthoritiesAccounts::<T>::put(&new_authorities_accounts);
		NextAuthorities::<T>::put(&next_authority_ids);
		NextAuthoritiesAccounts::<T>::put(&next_authorities_accounts);
		// Call set change handler to trigger the other pallet implementing this hook
		<T::OnAuthoritySetChangeHandler as OnAuthoritySetChangeHandler<
			T::AccountId,
			dkg_runtime_primitives::AuthoritySetId,
			T::DKGId,
		>>::on_authority_set_changed(new_authorities_accounts, next_id, new_authority_ids.clone());
		// Update the set id after changing
		AuthoritySetId::<T>::put(next_id);
		// Deposit a consensus log about the authority set change
		let log: DigestItem = DigestItem::Consensus(
			DKG_ENGINE_ID,
			ConsensusLog::AuthoritiesChange {
				next_authorities: AuthoritySet { authorities: new_authority_ids, id: next_id },
				next_queued_authorities: AuthoritySet {
					authorities: next_authority_ids,
					id: next_id + 1u64,
				},
			}
			.encode(),
		);
		<frame_system::Pallet<T>>::deposit_log(log);
		Self::refresh_keys();
	}

	fn initialize_authorities(authorities: &[T::DKGId], authority_account_ids: &[T::AccountId]) {
		if authorities.is_empty() {
			return
		}

		assert!(Authorities::<T>::get().is_empty(), "Authorities are already initialized!");

		Authorities::<T>::put(authorities);
		AuthoritySetId::<T>::put(0);
		// Like `pallet_session`, initialize the next validator set as well.
		NextAuthorities::<T>::put(authorities);
		CurrentAuthoritiesAccounts::<T>::put(authority_account_ids);
		NextAuthoritiesAccounts::<T>::put(authority_account_ids);

		<T::OnAuthoritySetChangeHandler as OnAuthoritySetChangeHandler<
			T::AccountId,
			dkg_runtime_primitives::AuthoritySetId,
			T::DKGId,
		>>::on_authority_set_changed(authority_account_ids.to_vec(), 0, authorities.to_vec());
	}

	fn submit_genesis_public_key_onchain(block_number: T::BlockNumber) -> Result<(), &'static str> {
		let mut lock = StorageLock::<Time>::new(AGGREGATED_PUBLIC_KEYS_AT_GENESIS_LOCK);
		{
			let _guard = lock.lock();
			let mut agg_key_ref = StorageValueRef::persistent(AGGREGATED_PUBLIC_KEYS_AT_GENESIS);
			let mut submit_at_ref = StorageValueRef::persistent(SUBMIT_GENESIS_KEYS_AT);
			const RECENTLY_SENT: &str = "Already submitted a key in this session";
			let submit_at = submit_at_ref.get::<T::BlockNumber>();

			if let Ok(Some(submit_at)) = submit_at {
				if block_number < submit_at {
					frame_support::log::debug!(target: "dkg", "Offchain worker skipping public key submmission");
					return Ok(())
				} else {
					submit_at_ref.clear();
				}
			} else {
				return Err(RECENTLY_SENT)
			}

			if !Self::dkg_public_key().1.is_empty() {
				agg_key_ref.clear();
				return Ok(())
			}

			let agg_keys = agg_key_ref.get::<AggregatedPublicKeys>();

			let signer = Signer::<T, T::OffChainAuthId>::all_accounts();
			if !signer.can_sign() {
				return Err(
					"No local accounts available. Consider adding one via `author_insertKey` RPC.",
				)
			}

			if let Ok(Some(agg_keys)) = agg_keys {
				let _res = signer.send_signed_transaction(|_account| Call::submit_public_key {
					keys_and_signatures: agg_keys.clone(),
				});

				agg_key_ref.clear();
			}

			Ok(())
		}
	}

	fn submit_next_public_key_onchain(block_number: T::BlockNumber) -> Result<(), &'static str> {
		let mut lock = StorageLock::<Time>::new(AGGREGATED_PUBLIC_KEYS_LOCK);
		{
			let _guard = lock.lock();

			let mut agg_key_ref = StorageValueRef::persistent(AGGREGATED_PUBLIC_KEYS);
			let mut submit_at_ref = StorageValueRef::persistent(SUBMIT_KEYS_AT);
			const RECENTLY_SENT: &str = "Already submitted a key in this session";
			let submit_at = submit_at_ref.get::<T::BlockNumber>();

			if let Ok(Some(submit_at)) = submit_at {
				if block_number < submit_at {
					frame_support::log::debug!(target: "dkg", "Offchain worker skipping public key submmission");
					return Ok(())
				} else {
					submit_at_ref.clear();
				}
			} else {
				return Err(RECENTLY_SENT)
			}

			if Self::next_dkg_public_key().is_some() {
				agg_key_ref.clear();
				return Ok(())
			}

			let agg_keys = agg_key_ref.get::<AggregatedPublicKeys>();

			let signer = Signer::<T, T::OffChainAuthId>::all_accounts();
			if !signer.can_sign() {
				return Err(
					"No local accounts available. Consider adding one via `author_insertKey` RPC.",
				)
			}

			// TODO: Check if next public key is none
			if let Ok(Some(agg_keys)) = agg_keys {
				let _res = signer.send_signed_transaction(|_account| {
					Call::submit_next_public_key { keys_and_signatures: agg_keys.clone() }
				});

				agg_key_ref.clear();
			}

			Ok(())
		}
	}

	fn submit_public_key_signature_onchain(
		_block_number: T::BlockNumber,
	) -> Result<(), &'static str> {
		let mut lock = StorageLock::<Time>::new(OFFCHAIN_PUBLIC_KEY_SIG_LOCK);
		{
			let _guard = lock.lock();

			let mut pub_key_sig_ref = StorageValueRef::persistent(OFFCHAIN_PUBLIC_KEY_SIG);

			if Self::next_public_key_signature().is_some() {
				pub_key_sig_ref.clear();
				return Ok(())
			}

			let refresh_proposal = pub_key_sig_ref.get::<RefreshProposalSigned>();

			let signer = Signer::<T, T::OffChainAuthId>::all_accounts();
			if !signer.can_sign() {
				return Err(
					"No local accounts available. Consider adding one via `author_insertKey` RPC.",
				)
			}

			if let Ok(Some(refresh_proposal)) = refresh_proposal {
				let _ =
					signer.send_signed_transaction(|_account| Call::submit_public_key_signature {
						signature_proposal: refresh_proposal.clone(),
					});
				frame_support::log::debug!(target: "dkg", "Offchain submitting public key sig onchain {:?}", refresh_proposal.signature);

				pub_key_sig_ref.clear();
			}

			Ok(())
		}
	}

	fn submit_misbehaviour_reports_onchain(
		_block_number: T::BlockNumber,
	) -> Result<(), &'static str> {
		let mut lock = StorageLock::<Time>::new(AGGREGATED_MISBEHAVIOUR_REPORTS_LOCK);
		{
			let _guard = lock.lock();

			let signer = Signer::<T, T::OffChainAuthId>::all_accounts();
			if !signer.can_sign() {
				return Err(
					"No local accounts available. Consider adding one via `author_insertKey` RPC.",
				)
			}

			let mut agg_reports_ref = StorageValueRef::persistent(AGGREGATED_MISBEHAVIOUR_REPORTS);
			let agg_misbehaviour_reports = agg_reports_ref.get::<AggregatedMisbehaviourReports>();

			if let Ok(Some(reports)) = agg_misbehaviour_reports {
				// If this offender has already been reported, don't report it again.
				if Self::misbehaviour_reports((reports.round_id, reports.offender.clone()))
					.is_some()
				{
					agg_reports_ref.clear();
					return Ok(())
				}

				let _ = signer.send_signed_transaction(|_account| {
					Call::submit_misbehaviour_reports { reports: reports.clone() }
				});

				frame_support::log::debug!(target: "dkg", "Offchain submitting reports onchain {:?}", reports);

				agg_reports_ref.clear();
			}

			Ok(())
		}
	}

	pub fn should_refresh(now: T::BlockNumber) -> bool {
		let (session_progress, ..) = <T::NextSessionRotation as EstimateNextSessionRotation<
			T::BlockNumber,
		>>::estimate_current_session_progress(now);
		if let Some(session_progress) = session_progress {
			let delay = RefreshDelay::<T>::get();
			let next_dkg_public_key_signature = Self::next_public_key_signature();
			return (delay <= session_progress) && next_dkg_public_key_signature.is_none()
		}
		false
	}

	pub fn refresh_keys() {
		// Update the active thresholds for the next session
		SignatureThreshold::<T>::put(NextSignatureThreshold::<T>::get());
		KeygenThreshold::<T>::put(NextKeygenThreshold::<T>::get());
		// Update the next thresholds for the next session
		NextSignatureThreshold::<T>::put(PendingSignatureThreshold::<T>::get());
		NextKeygenThreshold::<T>::put(PendingKeygenThreshold::<T>::get());
		// Update the keys for the next authorities
		let next_pub_key = Self::next_dkg_public_key();
		let next_pub_key_signature = Self::next_public_key_signature();
		let dkg_pub_key = Self::dkg_public_key();
		let pub_key_signature = Self::public_key_signature();
		NextDKGPublicKey::<T>::kill();
		NextPublicKeySignature::<T>::kill();
		let v = next_pub_key.zip(next_pub_key_signature);
		if let Some((next_pub_key, next_pub_key_signature)) = v {
			// Insert historical round metadata consisting of the current round's
			// public key before rotation, the next round's public key, and the refresh
			// signature signed by the current key refreshing the next.
			HistoricalRounds::<T>::insert(
				next_pub_key.0,
				RoundMetadata {
					curr_round_pub_key: dkg_pub_key.1.clone(),
					next_round_pub_key: next_pub_key.clone().1,
					refresh_signature: next_pub_key_signature.clone(),
				},
			);
			// Set new keys
			DKGPublicKey::<T>::put(next_pub_key.clone());
			DKGPublicKeySignature::<T>::put(next_pub_key_signature.clone());
			PreviousPublicKey::<T>::put(dkg_pub_key.clone());
			UsedSignatures::<T>::mutate(|val| {
				val.push(pub_key_signature.clone());
			});
			// Set refresh in progress to false
			RefreshInProgress::<T>::put(false);
			let log: DigestItem = DigestItem::Consensus(
				DKG_ENGINE_ID,
				ConsensusLog::<T::DKGId>::KeyRefresh {
					new_key_signature: next_pub_key_signature.clone(),
					old_public_key: dkg_pub_key.1,
					new_public_key: next_pub_key.1.clone(),
				}
				.encode(),
			);
			let uncompressed_pub_key = Self::decompress_public_key(next_pub_key.1.clone()).unwrap();
			<frame_system::Pallet<T>>::deposit_log(log);
			// Emit events so other front-end know that.
			Self::deposit_event(Event::PublicKeyChanged {
				uncompressed_pub_key,
				compressed_pub_key: next_pub_key.1,
			});
			Self::deposit_event(Event::PublicKeySignatureChanged {
				pub_key_sig: next_pub_key_signature,
			})
		}
	}

	pub fn max_extrinsic_delay(_block_number: T::BlockNumber) -> T::BlockNumber {
		let refresh_delay = Self::refresh_delay();
		let session_length = <T::NextSessionRotation as EstimateNextSessionRotation<
			T::BlockNumber,
		>>::average_session_length();

		Permill::from_percent(50) * (refresh_delay * session_length)
	}

	#[cfg(feature = "runtime-benchmarks")]
	pub fn set_dkg_public_key(key: Vec<u8>) {
		DKGPublicKey::<T>::put((0, key))
	}
}

impl<T: Config> sp_runtime::BoundToRuntimeAppPublic for Pallet<T> {
	type Public = T::DKGId;
}

impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T> {
	type Key = T::DKGId;

	fn on_genesis_session<'a, I: 'a>(validators: I)
	where
		I: Iterator<Item = (&'a T::AccountId, T::DKGId)>,
	{
		let mut authority_account_ids = Vec::new();
		let authorities = validators
			.map(|(l, k)| {
				authority_account_ids.push(l.clone());
				k
			})
			.collect::<Vec<_>>();

		Self::initialize_authorities(&authorities, &authority_account_ids);
	}

	// We want to run this function always because there are other factors(forcing a new era) that
	// can affect changes to the queued validator set that the session pallet will not take not of
	// until the next session, and this could cause the value of `changed` to be wrong, causing an
	// out of sync between this pallet and the session pallet. The `changed` value is true most of
	// the times except in rare cases, omitting  that check does not cause any harm, since this
	// function is light weight we already have a check in the change_authorities function that
	// would ensure the refresh is not run if the authority set has not changed.
	fn on_new_session<'a, I: 'a>(_changed: bool, validators: I, queued_validators: I)
	where
		I: Iterator<Item = (&'a T::AccountId, T::DKGId)>,
	{
		let mut authority_account_ids = Vec::new();
		let mut queued_authority_account_ids = Vec::new();
		let next_authorities = validators
			.map(|(l, k)| {
				authority_account_ids.push(l.clone());
				k
			})
			.collect::<Vec<_>>();

		let next_queued_authorities = queued_validators
			.map(|(acc, k)| {
				queued_authority_account_ids.push(acc.clone());
				k
			})
			.collect::<Vec<_>>();

		Self::change_authorities(
			next_authorities,
			next_queued_authorities,
			authority_account_ids,
			queued_authority_account_ids,
		);
	}

	fn on_disabled(i: u32) {
		let log: DigestItem = DigestItem::Consensus(
			DKG_ENGINE_ID,
			ConsensusLog::<T::DKGId>::OnDisabled(i as AuthorityIndex).encode(),
		);

		<frame_system::Pallet<T>>::deposit_log(log);
	}
}

impl<T: Config> IsMember<T::DKGId> for Pallet<T> {
	fn is_member(authority_id: &T::DKGId) -> bool {
		Self::authorities().iter().any(|id| id == authority_id)
	}
}

impl<T: Config> GetDKGPublicKey for Pallet<T> {
	fn dkg_key() -> Vec<u8> {
		Self::dkg_public_key().1
	}

	fn previous_dkg_key() -> Vec<u8> {
		Self::previous_public_key().1
	}
}
