use super::*;

pub fn total_borrowed_rate(value: f64) -> IntMaxBorrowAmount<Rate> {
	IntMaxBorrowAmount::UpToTotalBorrowed {
		advance_rate: Rate::from_float(value),
	}
}

pub fn outstanding_debt_rate(value: f64) -> IntMaxBorrowAmount<Rate> {
	IntMaxBorrowAmount::UpToOutstandingDebt {
		advance_rate: Rate::from_float(value),
	}
}

pub fn get_loan(loan_id: LoanId) -> ActiveLoan<Runtime> {
	ActiveLoans::<Runtime>::get(POOL_A)
		.into_iter()
		.find(|(id, _)| *id == loan_id)
		.unwrap()
		.1
}

pub fn current_loan_debt(loan_id: LoanId) -> Balance {
	match get_loan(loan_id).pricing() {
		ActivePricing::Internal(pricing) => pricing.interest.current_debt().unwrap(),
		ActivePricing::External(pricing) => pricing.interest.current_debt().unwrap(),
	}
}

pub fn current_extenal_pricing(loan_id: LoanId) -> ExternalActivePricing<Runtime> {
	match get_loan(loan_id).pricing() {
		ActivePricing::Internal(_) => panic!("expected external pricing"),
		ActivePricing::External(pricing) => pricing.clone(),
	}
}

pub fn borrower(loan_id: LoanId) -> AccountId {
	match CreatedLoan::<Runtime>::get(POOL_A, loan_id) {
		Some(created_loan) => *created_loan.borrower(),
		None => *ActiveLoans::<Runtime>::get(POOL_A)
			.into_iter()
			.find(|(id, _)| *id == loan_id)
			.expect("loan not found")
			.1
			.borrower(),
	}
}

pub fn current_loan_pv(loan_id: LoanId) -> Balance {
	get_loan(loan_id).present_value(POOL_A).unwrap()
}

pub fn interest_for(rate: f64, elapsed: Duration) -> f64 {
	(1.0 + rate / YEAR.as_secs() as f64).powi(elapsed.as_secs() as i32)
}

pub fn current_debt_for(interest: f64, balance: Balance) -> Balance {
	(interest * balance as f64) as Balance
}

pub fn set_up_policy(percentage: f64, penalty: f64) {
	MockPermissions::mock_has(|_, _, _| true);
	MockPools::mock_pool_exists(|_| true);
	MockChangeGuard::mock_released(move |_, _| {
		Ok(Change::Policy(
			vec![WriteOffRule::new(
				[WriteOffTrigger::PrincipalOverdue(SECONDS_PER_DAY)],
				Rate::from_float(percentage),
				Rate::from_float(penalty),
			)]
			.try_into()
			.unwrap(),
		))
	});

	Loans::apply_write_off_policy(RuntimeOrigin::signed(ANY), POOL_A, CHANGE_ID)
		.expect("successful apply");

	MockPermissions::mock_has(|_, _, _| panic!("no has() mock"));
	MockPools::mock_pool_exists(|_| panic!("no pool_exists() mock"));
}

pub fn base_internal_pricing() -> InternalPricing<Runtime> {
	InternalPricing {
		collateral_value: COLLATERAL_VALUE,
		max_borrow_amount: util::total_borrowed_rate(1.0),
		valuation_method: ValuationMethod::OutstandingDebt,
	}
}

pub fn dcf_internal_pricing() -> InternalPricing<Runtime> {
	InternalPricing {
		collateral_value: COLLATERAL_VALUE,
		max_borrow_amount: util::total_borrowed_rate(1.0),
		valuation_method: ValuationMethod::DiscountedCashFlow(DiscountedCashFlow {
			probability_of_default: Rate::from_float(DEFAULT_PROBABILITY_OF_DEFAULT),
			loss_given_default: Rate::from_float(DEFAULT_LOSS_GIVEN_DEFAULT),
			discount_rate: InterestRate::Fixed {
				rate_per_year: Rate::from_float(DEFAULT_DISCOUNT_RATE),
				compounding: CompoundingSchedule::Secondly,
			},
		}),
	}
}

pub fn dcf_internal_loan() -> LoanInfo<Runtime> {
	LoanInfo {
		pricing: Pricing::Internal(dcf_internal_pricing()),
		..base_internal_loan()
	}
}

pub fn default_interest_rate() -> InterestRate<Rate> {
	InterestRate::Fixed {
		rate_per_year: Rate::from_float(DEFAULT_INTEREST_RATE),
		compounding: CompoundingSchedule::Secondly,
	}
}

pub fn base_internal_loan() -> LoanInfo<Runtime> {
	LoanInfo {
		schedule: RepaymentSchedule {
			maturity: Maturity::Fixed {
				date: (now() + YEAR).as_secs(),
				extension: (YEAR / 2).as_secs(),
			},
			interest_payments: InterestPayments::OnceAtMaturity,
			pay_down_schedule: PayDownSchedule::None,
		},
		interest_rate: default_interest_rate(),
		collateral: ASSET_AA,
		pricing: Pricing::Internal(base_internal_pricing()),
		restrictions: LoanRestrictions {
			borrows: BorrowRestrictions::NotWrittenOff,
			repayments: RepayRestrictions::None,
		},
	}
}

pub fn base_external_pricing() -> ExternalPricing<Runtime> {
	ExternalPricing {
		price_id: REGISTER_PRICE_ID,
		max_borrow_amount: ExtMaxBorrowAmount::Quantity(QUANTITY),
		notional: NOTIONAL,
		max_price_variation: MAX_PRICE_VARIATION,
		with_linear_pricing: true,
	}
}

pub fn base_external_loan() -> LoanInfo<Runtime> {
	LoanInfo {
		schedule: RepaymentSchedule {
			maturity: Maturity::fixed((now() + YEAR).as_secs()),
			interest_payments: InterestPayments::OnceAtMaturity,
			pay_down_schedule: PayDownSchedule::None,
		},
		interest_rate: default_interest_rate(),
		collateral: ASSET_AA,
		pricing: Pricing::External(base_external_pricing()),
		restrictions: LoanRestrictions {
			borrows: BorrowRestrictions::NotWrittenOff,
			repayments: RepayRestrictions::None,
		},
	}
}

pub fn create_loan(loan: LoanInfo<Runtime>) -> LoanId {
	create_loan_by(loan, BORROWER)
}

pub fn create_loan_by(loan: LoanInfo<Runtime>, borrower: AccountId) -> LoanId {
	MockPermissions::mock_has(|_, _, _| true);
	MockPools::mock_pool_exists(|_| true);
	MockPools::mock_account_for(|_| POOL_A_ACCOUNT);
	MockPrices::mock_get(|_, _| Ok((PRICE_VALUE, BLOCK_TIME_MS)));

	Loans::create(RuntimeOrigin::signed(borrower), POOL_A, loan).expect("successful creation");

	MockPermissions::mock_has(|_, _, _| panic!("no has() mock"));
	MockPools::mock_pool_exists(|_| panic!("no pool_exists() mock"));
	MockPools::mock_account_for(|_| panic!("no account_for() mock"));
	MockPrices::mock_get(|_, _| panic!("no get() mock"));

	LastLoanId::<Runtime>::get(POOL_A)
}

pub fn borrow_loan(loan_id: LoanId, borrow_amount: PrincipalInput<Runtime>) {
	MockPools::mock_withdraw(|_, _, _| Ok(()));
	MockPrices::mock_get(|_, _| Ok((PRICE_VALUE, BLOCK_TIME_MS)));
	MockPrices::mock_register_id(|_, _| Ok(()));

	Loans::borrow(
		RuntimeOrigin::signed(borrower(loan_id)),
		POOL_A,
		loan_id,
		borrow_amount,
	)
	.expect("successful borrowing");

	MockPools::mock_withdraw(|_, _, _| panic!("no withdraw() mock"));
	MockPrices::mock_get(|_, _| panic!("no get() mock"));
	MockPrices::mock_register_id(|_, _| panic!("no register_id() mock"));
}

pub fn repay_loan(loan_id: LoanId, repay_amount: PrincipalInput<Runtime>) {
	MockPools::mock_deposit(|_, _, _| Ok(()));
	MockPrices::mock_get(|_, _| Ok((PRICE_VALUE, BLOCK_TIME_MS)));

	Loans::repay(
		RuntimeOrigin::signed(borrower(loan_id)),
		POOL_A,
		loan_id,
		RepaidInput {
			principal: repay_amount,
			interest: u128::MAX,
			unscheduled: 0,
		},
	)
	.expect("successful repaying");

	MockPools::mock_deposit(|_, _, _| panic!("no deposit() mock"));
	MockPrices::mock_get(|_, _| panic!("no get() mock"));
}

pub fn write_off_loan(loan_id: LoanId) {
	set_up_policy(POLICY_PERCENTAGE, POLICY_PENALTY);
	MockPrices::mock_get(|_, _| Ok((PRICE_VALUE, BLOCK_TIME_MS)));

	Loans::write_off(RuntimeOrigin::signed(ANY), POOL_A, loan_id).expect("successful write off");

	MockPrices::mock_get(|_, _| panic!("no get() mock"));
}

pub fn close_loan(loan_id: LoanId) {
	MockPrices::mock_unregister_id(|_, _| Ok(()));

	Loans::close(RuntimeOrigin::signed(borrower(loan_id)), POOL_A, loan_id)
		.expect("successful clossing");

	MockPrices::mock_get(|_, _| panic!("no unregister_id() mock"));
}
