use super::{
    constants::{EXCHANGE_PRICE_SCALE_FACTOR, FOUR_DECIMALS, SECONDS_PER_YEAR},
    errors::ErrorCodes,
};
use crate::liquidity;
use crate::{
    math::{casting::Cast, safe_math::SafeMath},
    programs::vaults::{
        accounts::{TokenReserve, VaultConfig, VaultState},
        ID,
    },
};
use anchor_lang::prelude::Pubkey;
use anyhow::Result;
use std::ops::Add;

use super::ticks::MAX_TICK;

pub fn get_vault_state(vault_id: u16) -> Pubkey {
    Pubkey::find_program_address(&[b"vault_state", &vault_id.to_le_bytes()], &ID).0
}

pub fn get_vault_config(vault_id: u16) -> Pubkey {
    Pubkey::find_program_address(&[b"vault_config", &vault_id.to_le_bytes()], &ID).0
}

pub fn get_vault_metadata(vault_id: u16) -> Pubkey {
    Pubkey::find_program_address(&[b"vault_metadata", &vault_id.to_le_bytes()], &ID).0
}

pub fn get_vault_admin() -> Pubkey {
    Pubkey::find_program_address(&[b"vault_admin"], &ID).0
}

pub fn get_tick(vault_id: u16, tick: i32) -> Pubkey {
    Pubkey::find_program_address(
        &[
            b"tick",
            &vault_id.to_le_bytes(),
            &tick.add(MAX_TICK).to_le_bytes(),
        ],
        &ID,
    )
    .0
}

pub fn get_branch(vault_id: u16, branch_id: u32) -> Pubkey {
    Pubkey::find_program_address(
        &[b"branch", &vault_id.to_le_bytes(), &branch_id.to_le_bytes()],
        &ID,
    )
    .0
}

pub fn get_tick_has_debt(vault_id: u16, index: u8) -> Pubkey {
    Pubkey::find_program_address(
        &[
            b"tick_has_debt",
            &vault_id.to_le_bytes(),
            &index.to_le_bytes(),
        ],
        &ID,
    )
    .0
}

impl VaultState {
    pub fn add_absorbed_debt_amount(&mut self, debt: u128) -> Result<()> {
        self.absorbed_debt_amount = self.absorbed_debt_amount.safe_add(debt)?;

        Ok(())
    }

    pub fn add_absorbed_col_amount(&mut self, col: u128) -> Result<()> {
        self.absorbed_col_amount = self.absorbed_col_amount.safe_add(col)?;

        Ok(())
    }

    fn get_exchange_prices(&self) -> anyhow::Result<(u128, u128, u128, u128)> {
        let vault_supply_ex_price: u128 = self.vault_supply_exchange_price.cast()?;
        let vault_borrow_ex_price: u128 = self.vault_borrow_exchange_price.cast()?;
        let old_liq_supply_ex_price: u128 = self.liquidity_supply_exchange_price.cast()?;
        let old_liq_borrow_ex_price: u128 = self.liquidity_borrow_exchange_price.cast()?;

        Ok((
            vault_supply_ex_price,
            vault_borrow_ex_price,
            old_liq_supply_ex_price,
            old_liq_borrow_ex_price,
        ))
    }

    pub fn load_exchange_prices(
        &self,
        vault_config: &VaultConfig,
        supply_token_reserves: &TokenReserve,
        borrow_token_reserves: &TokenReserve,
    ) -> anyhow::Result<(u128, u128, u128, u128)> {
        // latest vault exchange prices with old liquidity exchange prices
        let (
            mut vault_supply_ex_price,
            mut vault_borrow_ex_price,
            old_liq_supply_ex_price,
            old_liq_borrow_ex_price,
        ) = self.get_exchange_prices()?;

        let (liq_supply_ex_price, _) = supply_token_reserves.calculate_exchange_prices()?;
        let (_, liq_borrow_ex_price) = borrow_token_reserves.calculate_exchange_prices()?;

        if liq_supply_ex_price < old_liq_supply_ex_price
            || liq_borrow_ex_price < old_liq_borrow_ex_price
        {
            // new liquidity exchange price is < than the old one. liquidity exchange price should only ever increase.
            // If not, something went wrong and avoid proceeding with unknown outcome.
            return Err(ErrorCodes::VaultLiquidityExchangePriceUnexpected.into());
        }

        let vault_supply_ex_price_old = vault_supply_ex_price;

        // liquidity Exchange Prices always increases in next block. Hence subtraction with old will never be negative
        // uint64 * 1e18 is the max the number that could be
        // Calculating increase in supply exchange price w.r.t last stored liquidity's exchange price
        let liq_supply_increase_in_percent: u128 = liq_supply_ex_price
            .safe_mul(EXCHANGE_PRICE_SCALE_FACTOR)?
            .safe_div(old_liq_supply_ex_price)?;

        // It's extremely hard the exchange prices to overflow even in 100 years but if it does it's not an
        // issue here as we are not updating on storage last stored vault's supply token exchange price
        vault_supply_ex_price = vault_supply_ex_price
            .safe_mul(liq_supply_increase_in_percent)?
            .safe_div(EXCHANGE_PRICE_SCALE_FACTOR)?;

        let time_diff = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)?
            .as_secs()
            .cast::<u128>()?
            .safe_sub(self.last_update_timestamp.cast()?)?;

        if vault_config.supply_rate_magnifier != 0 {
            let supply_rate_change: u128 = vault_supply_ex_price_old
                .safe_mul(time_diff)?
                .safe_mul(vault_config.supply_rate_magnifier.abs().cast()?)?
                .safe_div(FOUR_DECIMALS)?
                .safe_div(SECONDS_PER_YEAR)?;

            if vault_config.supply_rate_magnifier > 0 {
                vault_supply_ex_price = vault_supply_ex_price.safe_add(supply_rate_change)?;
            } else {
                vault_supply_ex_price = vault_supply_ex_price.safe_sub(supply_rate_change)?;
            }
        }

        let vault_borrow_ex_price_old = vault_borrow_ex_price;

        // Calculating increase in borrow exchange price w.r.t last stored liquidity's exchange price
        let liq_borrow_increase_in_percent: u128 = liq_borrow_ex_price
            .safe_mul(EXCHANGE_PRICE_SCALE_FACTOR)?
            .safe_div(old_liq_borrow_ex_price)?;

        vault_borrow_ex_price = vault_borrow_ex_price
            .safe_mul(liq_borrow_increase_in_percent)?
            .safe_div(EXCHANGE_PRICE_SCALE_FACTOR)?;

        if vault_config.borrow_rate_magnifier != 0 {
            let borrow_rate_change: u128 = vault_borrow_ex_price_old
                .safe_mul(time_diff)?
                .safe_mul(vault_config.borrow_rate_magnifier.abs().cast()?)?
                .safe_div(FOUR_DECIMALS)?
                .safe_div(SECONDS_PER_YEAR)?;

            if vault_config.borrow_rate_magnifier > 0 {
                vault_borrow_ex_price = vault_borrow_ex_price.safe_add(borrow_rate_change)?;
            } else {
                vault_borrow_ex_price = vault_borrow_ex_price.safe_sub(borrow_rate_change)?;
            }
        }

        Ok((
            liq_supply_ex_price,
            liq_borrow_ex_price,
            vault_supply_ex_price,
            vault_borrow_ex_price,
        ))
    }
}

pub const EXCHANGE_PRICE_RATE_OUTPUT_DECIMALS: u128 = 10u128.pow(17);

impl TokenReserve {
    fn get_with_interest_vs_free_ratio(
        &self,
        with_interest: u128,
        interest_free: u128,
    ) -> Result<u128> {
        let ratio: u128 = if with_interest > interest_free {
            // ratio with interest being larger
            interest_free
                .safe_mul(FOUR_DECIMALS)?
                .safe_div(with_interest)?
        } else if with_interest < interest_free {
            // ratio with interest free being larger
            with_interest
                .safe_mul(FOUR_DECIMALS)?
                .safe_div(interest_free)?
        } else {
            // amounts match exactly
            if with_interest > 0 {
                // amounts are not 0 -> set ratio to 1
                FOUR_DECIMALS
            } else {
                // if total = 0
                0
            }
        };

        Ok(ratio)
    }

    pub fn get_total_supply_with_interest(&self) -> Result<u128> {
        Ok(self.total_supply_with_interest.cast()?)
    }

    pub fn get_total_supply_interest_free(&self) -> Result<u128> {
        Ok(self.total_supply_interest_free.cast()?)
    }

    fn get_supply_ratio(&self) -> Result<u128> {
        let supply_ratio: u128 = self.get_with_interest_vs_free_ratio(
            self.get_total_supply_with_interest()?,
            self.get_total_supply_interest_free()?,
        )?;

        Ok(supply_ratio)
    }

    pub fn get_total_borrow_with_interest(&self) -> Result<u128> {
        Ok(self.total_borrow_with_interest.cast()?)
    }

    pub fn get_total_borrow_interest_free(&self) -> Result<u128> {
        Ok(self.total_borrow_interest_free.cast()?)
    }

    fn get_borrow_ratio(&self) -> Result<u128> {
        let borrow_ratio: u128 = self.get_with_interest_vs_free_ratio(
            self.get_total_borrow_with_interest()?,
            self.get_total_borrow_interest_free()?,
        )?;

        Ok(borrow_ratio)
    }

    pub fn calculate_exchange_prices(&self) -> Result<(u128, u128)> {
        let mut supply_exchange_price: u128 = self.supply_exchange_price.cast()?;
        let mut borrow_exchange_price: u128 = self.borrow_exchange_price.cast()?;

        if supply_exchange_price == 0 || borrow_exchange_price == 0 {
            return Err(liquidity::errors::ErrorCodes::ExchangePriceZero.into());
        }

        let borrow_rate: u128 = self.borrow_rate.cast()?;

        let timestamp: u128 = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)?
            .as_secs()
            .cast()?; // current block timestamp
        let last_update_timestamp: u128 = self.last_update_timestamp.cast()?;

        // last timestamp can not be > current timestamp
        let seconds_since_last_update: u128 = timestamp.safe_sub(last_update_timestamp)?;

        if seconds_since_last_update == 0
            || borrow_rate == 0
            || self.total_borrow_with_interest == 0
        {
            // if no time passed, borrow rate is 0, or no raw borrowings: no exchange price update needed
            return Ok((supply_exchange_price, borrow_exchange_price));
        }

        // calculate new borrow exchange price.
        // formula borrowExchangePriceIncrease: previous price * borrow rate * secondsSinceLastUpdate_.
        borrow_exchange_price = borrow_exchange_price.safe_add(
            borrow_exchange_price
                .safe_mul(borrow_rate)?
                .safe_mul(seconds_since_last_update)?
                .safe_div(SECONDS_PER_YEAR.safe_mul(FOUR_DECIMALS)?)?,
        )?;

        // FOR SUPPLY EXCHANGE PRICE:
        // all yield paid by borrowers (in mode with interest) goes to suppliers in mode with interest.
        // formula: previous price * supply rate * secondsSinceLastUpdate_.
        // where supply rate = (borrow rate  - revenueFee%) * ratioSupplyYield. And
        // ratioSupplyYield = utilization * supplyRatio * borrowRatio
        //
        // Example:
        // supplyRawInterest is 80, supplyInterestFree is 20. totalSupply is 100. BorrowedRawInterest is 50.
        // BorrowInterestFree is 10. TotalBorrow is 60. borrow rate 40%, revenueFee 10%.
        // yield is 10 (so half a year must have passed).
        // supplyRawInterest must become worth 89. totalSupply must become 109. BorrowedRawInterest must become 60.
        // borrowInterestFree must still be 10. supplyInterestFree still 20. totalBorrow 70.
        // supplyExchangePrice would have to go from 1 to 1,125 (+ 0.125). borrowExchangePrice from 1 to 1,2 (+0.2).
        // utilization is 60%. supplyRatio = 20 / 80 = 25% (only 80% of lenders receiving yield).
        // borrowRatio = 10 / 50 = 20% (only 83,333% of borrowers paying yield):
        // x of borrowers paying yield = 100% - (20 / (100 + 20)) = 100% - 16.6666666% = 83,333%.
        // ratioSupplyYield = 60% * 83,33333% * (100% + 25%) = 62,5%
        // supplyRate = (40% * (100% - 10%)) * 62,5% = 36% * 62,5% = 22.5%
        // increase in supplyExchangePrice, assuming 100 as previous price.
        // 100 * 22,5% * 1/2 (half a year) = 0,1125.
        // cross-check supplyRawInterest worth = 80 * 1.1125 = 89. totalSupply worth = 89 + 20.

        // -------------- 1. calculate ratioSupplyYield --------------------------------
        // step1: utilization * supplyRatio (or actually part of lenders receiving yield)

        if self.total_supply_with_interest == 0 {
            // if no raw supply: no exchange price update needed
            return Ok((supply_exchange_price, borrow_exchange_price));
        }

        let supply_ratio = self.get_supply_ratio()?;

        // this ratio_supply_yield doesn't contain the borrow_ratio part
        let mut ratio_supply_yield: u128 =
            if self.total_supply_with_interest < self.total_supply_interest_free {
                // ratio is supplyWithInterest / supplyInterestFree (supplyInterestFree is bigger)

                if supply_ratio == 0 {
                    // @dev if supply_ratio == 0 and supply interest free > with interest then
                    // no one is earning interest or total_supply is 0, so no exchange price update needed.
                    // this covers the case where supply with interest exists but it is tiny compared to interest free supply.

                    return Ok((supply_exchange_price, borrow_exchange_price));
                }

                let supply_ratio: u128 = EXCHANGE_PRICE_RATE_OUTPUT_DECIMALS
                    .safe_mul(FOUR_DECIMALS)?
                    .safe_div(supply_ratio)?;

                // Note: case where temp_ == 0 (only supplyInterestFree, no yield) already covered by early return
                // in the if statement a little above.

                // based on above example but supplyRawInterest is 20, supplyInterestFree is 80. no fee.
                // supplyRawInterest must become worth 30. totalSupply must become 110.
                // supplyExchangePrice would have to go from 1 to 1,5. borrowExchangePrice from 1 to 1,2.
                // so ratioSupplyYield must come out as 2.5 (250%).
                // supplyRatio would be (20 * 10_000 / 80) = 2500. but must be inverted.

                let utilization: u128 = self.last_utilization.cast()?;

                utilization
                    .safe_mul(EXCHANGE_PRICE_RATE_OUTPUT_DECIMALS.safe_add(supply_ratio)?)?
                    .safe_div(FOUR_DECIMALS)?
            } else {
                let utilization: u128 = self.last_utilization.cast()?;

                // when with interest = 1e10 and interest free = 100, then ratio is 100 * 1e4 / 1e10 = 0
                // when only with interest exists, then also ratio is 0
                // so allowing the supply_ratio 0 case here is fine.

                // if ratio == 0 then only supplyWithInterest => full yield. ratio is already 0
                // e.g. 5_000 * 10_000 + (20 * 10_000 / 80) / 10_000 = 5000 * 12500 / 10000 = 6250 (=62.5%).
                // 1e17 * utilization * (100% + supplyRatio) / 100%
                utilization
                    .safe_mul(EXCHANGE_PRICE_RATE_OUTPUT_DECIMALS)?
                    .safe_mul(FOUR_DECIMALS.safe_add(supply_ratio)?)?
                    .safe_div(FOUR_DECIMALS.safe_mul(FOUR_DECIMALS)?)?
            };

        let borrow_ratio = self.get_borrow_ratio()?;

        let borrow_ratio = if self.total_borrow_with_interest < self.total_borrow_interest_free {
            // ratio is borrowWithInterest / borrowInterestFree (borrowInterestFree is bigger)
            // borrowRatio_ => x of total borrowers paying yield. scale to 1e17.

            // Note: case where borrowRatio_ == 0 (only borrowInterestFree, no yield) already covered
            // at the beginning of the method by early return if `borrowRatio_ == 1`.

            // based on above example but borrowRawInterest is 10, borrowInterestFree is 50. no fee. borrowRatio = 20%.
            // so only 16.66% of borrowers are paying yield. so the 100% - part of the formula is not needed.
            // x of borrowers paying yield = (borrowRatio / (100 + borrowRatio)) = 16.6666666%
            // borrowRatio_ => x of total borrowers paying yield. scale to 1e17.
            let _borrow_ratio: u128 = (borrow_ratio)
                .safe_mul(EXCHANGE_PRICE_RATE_OUTPUT_DECIMALS)?
                .safe_div(FOUR_DECIMALS.safe_add(borrow_ratio)?)?;

            // max value here for borrowRatio_ is (1e31 / (1e4 + 1e4))= 5e26 (= 50% of borrowers paying yield).
            _borrow_ratio
        } else {
            // ratio is borrowInterestFree / borrowWithInterest (borrowWithInterest is bigger)
            // borrowRatio_ => x of total borrowers paying yield. scale to 1e17.

            // x of borrowers paying yield = 100% - (borrowRatio / (100 + borrowRatio)) = 100% - 16.6666666% = 83,333%.
            let _borrow_ratio: u128 = EXCHANGE_PRICE_RATE_OUTPUT_DECIMALS.safe_sub(
                borrow_ratio
                    .safe_mul(EXCHANGE_PRICE_RATE_OUTPUT_DECIMALS)?
                    .safe_div(FOUR_DECIMALS.safe_add(borrow_ratio)?)?,
            )?;
            // borrowRatio can never be > 100%. so max subtraction can be 100% - 100% / 200%.
            // or if borrowRatio_ is 0 -> 100% - 0. or if borrowRatio_ is 1 -> 100% - 1 / 101.
            // max value here for borrowRatio_ is 1e17 - 0 = 1e17 (= 100% of borrowers paying yield).

            _borrow_ratio
        };

        ratio_supply_yield = ratio_supply_yield
            .safe_mul(borrow_ratio)?
            .safe_mul(FOUR_DECIMALS)?
            .safe_div(EXCHANGE_PRICE_RATE_OUTPUT_DECIMALS)?
            .safe_div(EXCHANGE_PRICE_RATE_OUTPUT_DECIMALS)?;

        // 2. calculate supply rate
        // supply rate (borrow rate  - revenueFee%) * ratioSupplyYield.
        // division part is done in next step to increase precision. (divided by 2x FOUR_DECIMALS, fee + borrowRate)
        // Note that all calculation divisions for supplyExchangePrice are rounded down.
        // Note supply rate can be bigger than the borrowRate, e.g. if there are only few lenders with interest
        // but more suppliers not earning interest.
        let supply_rate: u128 = borrow_rate
            .safe_mul(ratio_supply_yield)?
            .safe_mul(FOUR_DECIMALS.safe_sub(self.fee_on_interest.cast()?)?)?;
        // fee can not be > 100%. max possible = 65535 * ~1.64e8 * 1e4 =~1.074774e17.

        // 3. calculate increase in supply exchange price
        supply_exchange_price = supply_exchange_price.safe_add(
            supply_exchange_price
                .safe_mul(supply_rate)?
                .safe_mul(seconds_since_last_update)?
                .safe_div(SECONDS_PER_YEAR.safe_mul(FOUR_DECIMALS)?)?
                .safe_div(FOUR_DECIMALS.safe_mul(FOUR_DECIMALS)?)?,
        )?;

        Ok((supply_exchange_price, borrow_exchange_price))
    }
}
