use cfg_traits::{
	fee::{FeeAmountProration, PoolFeeBucket},
	Seconds,
};
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use sp_arithmetic::FixedPointOperand;
use sp_runtime::{traits::Get, BoundedVec, RuntimeDebug};
use sp_std::vec::Vec;
use crate::fixed_point::FixedPointNumberExtension;
#[derive(Debug, Encode, PartialEq, Eq, Decode, Clone, TypeInfo, MaxEncodedLen)]
pub struct TrancheMetadata<StringLimit: Get<u32>> {
	pub token_name: BoundedVec<u8, StringLimit>,
	pub token_symbol: BoundedVec<u8, StringLimit>,
}
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub struct PoolMetadata<MetaSize>
where
	MetaSize: Get<u32>,
{
	pub metadata: BoundedVec<u8, MetaSize>,
}
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub enum PoolRegistrationStatus {
	Registered,
	Unregistered,
}
#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub struct PoolNav<Balance> {
	pub nav_aum: Balance,
	pub nav_fees: Balance,
	pub reserve: Balance,
	pub total: Balance,
}
#[derive(Debug, Encode, Decode, TypeInfo, MaxEncodedLen, PartialEq, Eq, Clone)]
pub struct PoolFee<AccountId, FeeId, FeeAmounts> {
	pub destination: AccountId,
	pub editor: PoolFeeEditor<AccountId>,
	pub amounts: FeeAmounts,
	pub id: FeeId,
}
#[derive(Debug, Encode, Decode, TypeInfo, MaxEncodedLen, PartialEq, Eq, Clone)]
pub struct PoolFeeInfo<AccountId, Balance, Rate> {
	pub destination: AccountId,
	pub editor: PoolFeeEditor<AccountId>,
	pub fee_type: PoolFeeType<Balance, Rate>,
}
impl<AccountId, Balance, FeeId, Rate> PoolFee<AccountId, FeeId, PoolFeeAmounts<Balance, Rate>>
where
	Balance: Default,
{
	pub fn from_info(fee: PoolFeeInfo<AccountId, Balance, Rate>, fee_id: FeeId) -> Self {
		let payable = match fee.fee_type {
			PoolFeeType::ChargedUpTo { .. } => PayableFeeAmount::UpTo(Balance::default()),
			PoolFeeType::Fixed { .. } => PayableFeeAmount::AllPending,
		};
		let amount = PoolFeeAmounts {
			payable,
			fee_type: fee.fee_type,
			pending: Balance::default(),
			disbursement: Balance::default(),
		};
		Self {
			amounts: amount,
			destination: fee.destination,
			editor: fee.editor,
			id: fee_id,
		}
	}
}
#[derive(Debug, Encode, Decode, TypeInfo, MaxEncodedLen, PartialEq, Eq, Clone)]
pub enum PoolFeeEditor<AccountId> {
	Root,
	Account(AccountId),
}
impl<AccountId> From<PoolFeeEditor<AccountId>> for Option<AccountId> {
	fn from(editor: PoolFeeEditor<AccountId>) -> Option<AccountId> {
		match editor {
			PoolFeeEditor::Account(acc) => Some(acc),
			_ => None,
		}
	}
}
#[derive(Debug, Encode, Decode, TypeInfo, MaxEncodedLen, PartialEq, Eq, Clone)]
pub enum PoolFeeType<Balance, Rate> {
	Fixed { limit: PoolFeeAmount<Balance, Rate> },
	ChargedUpTo { limit: PoolFeeAmount<Balance, Rate> },
}
#[derive(Debug, Encode, Decode, TypeInfo, MaxEncodedLen, PartialEq, Eq, Clone)]
pub struct PoolFeeAmounts<Balance, Rate> {
	pub fee_type: PoolFeeType<Balance, Rate>,
	pub pending: Balance,
	pub disbursement: Balance,
	pub payable: PayableFeeAmount<Balance>,
}
#[derive(Debug, Encode, Decode, TypeInfo, MaxEncodedLen, PartialEq, Eq, Clone)]
pub enum PayableFeeAmount<Balance> {
	AllPending,
	UpTo(Balance),
}
impl<Balance, Rate> PoolFeeAmounts<Balance, Rate> {
	pub fn limit(&self) -> &PoolFeeAmount<Balance, Rate> {
		match &self.fee_type {
			PoolFeeType::Fixed { limit } | PoolFeeType::ChargedUpTo { limit } => limit,
		}
	}
}
#[derive(Debug, Encode, Decode, TypeInfo, MaxEncodedLen, PartialEq, Eq, Clone)]
pub enum PoolFeeAmount<Balance, Rate> {
	ShareOfPortfolioValuation(Rate),
	AmountPerSecond(Balance),
}
impl<Balance, Rate> FeeAmountProration<Balance, Rate, Seconds> for PoolFeeAmount<Balance, Rate>
where
	Rate: FixedPointNumberExtension,
	Balance: From<Seconds> + FixedPointOperand + sp_std::ops::Div<Output = Balance>,
{
	fn saturated_prorated_amount(&self, portfolio_valuation: Balance, period: Seconds) -> Balance {
		match self {
			PoolFeeAmount::ShareOfPortfolioValuation(_) => {
				let proration: Rate =
					<Self as FeeAmountProration<Balance, Rate, Seconds>>::saturated_prorated_rate(
						self,
						portfolio_valuation,
						period,
					);
				proration.saturating_mul_int(portfolio_valuation)
			}
			PoolFeeAmount::AmountPerSecond(amount) => amount.saturating_mul(period.into()),
		}
	}
	fn saturated_prorated_rate(&self, portfolio_valuation: Balance, period: Seconds) -> Rate {
		match self {
			PoolFeeAmount::ShareOfPortfolioValuation(rate) => {
				saturated_rate_proration(*rate, period)
			}
			PoolFeeAmount::AmountPerSecond(_) => {
				let prorated_amount: Balance = <Self as FeeAmountProration<
					Balance,
					Rate,
					Seconds,
				>>::saturated_prorated_amount(
					self, portfolio_valuation, period
				);
				Rate::saturating_from_rational(prorated_amount, portfolio_valuation)
			}
		}
	}
}
pub fn saturated_balance_proration<
	Balance: From<Seconds> + FixedPointOperand + sp_std::ops::Div<Output = Balance>,
>(
	annual_amount: Balance,
	period: Seconds,
) -> Balance {
	let amount = annual_amount.saturating_mul(period.into());
	amount.div(cfg_primitives::SECONDS_PER_YEAR.into())
}
pub fn saturated_rate_proration<Rate: FixedPointNumberExtension>(
	annual_rate: Rate,
	period: Seconds,
) -> Rate {
	let rate = annual_rate.saturating_mul(Rate::saturating_from_integer::<u64>(period));
	rate.saturating_div_ceil(&Rate::saturating_from_integer::<u64>(
		cfg_primitives::SECONDS_PER_YEAR,
	))
}
#[derive(Decode, Encode, TypeInfo)]
pub struct PoolFeesOfBucket<FeeId, AccountId, Balance, Rate> {
	pub bucket: PoolFeeBucket,
	pub fees: Vec<PoolFee<AccountId, FeeId, PoolFeeAmounts<Balance, Rate>>>,
}
pub type PoolFeesList<FeeId, AccountId, Balance, Rate> =
	Vec<PoolFeesOfBucket<FeeId, AccountId, Balance, Rate>>;
#[cfg(test)]
mod tests {
	use super::*;
	mod saturated_proration {
		use cfg_primitives::{CFG, DAYS, SECONDS_PER_YEAR};
		use sp_arithmetic::{
			traits::{One, Zero},
			FixedPointNumber,
		};
		use super::*;
		use crate::fixed_point::{Quantity, Rate};
		type Balance = u128;
		#[test]
		fn balance_zero() {
			assert_eq!(
				saturated_balance_proration::<Balance>(SECONDS_PER_YEAR.into(), 0),
				0
			);
			assert_eq!(
				saturated_balance_proration::<Balance>(0u128, SECONDS_PER_YEAR),
				0
			);
			assert_eq!(
				saturated_balance_proration::<Balance>((SECONDS_PER_YEAR - 1).into(), 1),
				0
			);
			assert_eq!(
				saturated_balance_proration::<Balance>(1u128, SECONDS_PER_YEAR - 1),
				0
			);
		}
		#[test]
		fn balance_one() {
			assert_eq!(
				saturated_balance_proration::<Balance>(SECONDS_PER_YEAR.into(), 1),
				1u128
			);
			assert_eq!(
				saturated_balance_proration::<Balance>(1u128, SECONDS_PER_YEAR),
				1u128
			);
		}
		#[test]
		fn balance_overflow() {
			assert_eq!(
				saturated_balance_proration::<Balance>(u128::MAX, u64::MAX),
				u128::MAX / u128::from(SECONDS_PER_YEAR)
			);
		}
		#[test]
		fn rate_zero() {
			assert_eq!(
				saturated_rate_proration::<Rate>(Rate::from_integer(SECONDS_PER_YEAR.into()), 0),
				Rate::zero()
			);
			assert_eq!(
				saturated_rate_proration::<Rate>(Rate::zero(), SECONDS_PER_YEAR),
				Rate::zero()
			);
			assert!(
				saturated_rate_proration::<Rate>(
					Rate::from_integer((SECONDS_PER_YEAR - 1).into()),
					1
				) > Rate::zero()
			);
			assert!(
				saturated_rate_proration::<Rate>(Rate::one(), SECONDS_PER_YEAR - 1) > Rate::zero()
			);
		}
		#[test]
		fn rate_one() {
			assert_eq!(
				saturated_rate_proration::<Rate>(Rate::from_integer(SECONDS_PER_YEAR.into()), 1),
				Rate::one()
			);
			assert_eq!(
				saturated_rate_proration::<Rate>(Rate::one(), SECONDS_PER_YEAR),
				Rate::one()
			);
		}
		#[test]
		fn rate_overflow() {
			let left_bound = Rate::from_integer(10790);
			let right_bound = Rate::from_integer(10791);
			let rate = saturated_rate_proration::<Rate>(
				Rate::from_integer(u128::from(u128::MAX / 10u128.pow(27))),
				1,
			);
			assert!(left_bound < rate);
			assert!(rate < right_bound);
			assert!(saturated_rate_proration::<Rate>(Rate::one(), u64::MAX) > left_bound);
			assert!(saturated_rate_proration::<Rate>(Rate::one(), u64::MAX) < right_bound);
			assert!(saturated_rate_proration::<Rate>(Rate::from_integer(2), u64::MAX) > left_bound);
			assert!(
				saturated_rate_proration::<Rate>(Rate::from_integer(2), u64::MAX) < right_bound
			);
		}
		#[test]
		fn precision_quantity_vs_rate() {
			let period = (DAYS / 4) as Seconds;
			let nav_multiplier = 1_000_000;
			let nav = nav_multiplier * CFG;
			let q_proration = saturated_rate_proration::<Quantity>(
				Quantity::checked_from_rational(1, 100).unwrap(),
				period,
			);
			let r_proration = saturated_rate_proration::<Rate>(
				Rate::checked_from_rational(1, 100).unwrap(),
				period,
			);
			let q_amount = q_proration.saturating_mul_int(nav);
			let r_amount = r_proration.saturating_mul_int(nav);
			let r_amount_rounded_up = (r_amount / (nav_multiplier) + 1) * (nav_multiplier);
			assert_eq!(q_amount, r_amount_rounded_up);
		}
	}
}