/* eslint-disable @typescript-eslint/no-unused-vars */
import {
  address,
  Address,
  fetchEncodedAccount,
  fetchEncodedAccounts,
  GetAccountInfoApi,
  GetMultipleAccountsApi,
  Rpc,
} from "@solana/kit"
/* eslint-enable @typescript-eslint/no-unused-vars */
import BN from "bn.js" // eslint-disable-line @typescript-eslint/no-unused-vars
import * as borsh from "@coral-xyz/borsh" // eslint-disable-line @typescript-eslint/no-unused-vars
import { borshAddress } from "../utils" // eslint-disable-line @typescript-eslint/no-unused-vars
import * as types from "../../../@codegen/klend/types" // eslint-disable-line @typescript-eslint/no-unused-vars
import { PROGRAM_ID } from "../../../@codegen/klend/programId"
import { Obligation, ObligationFields } from '../../../@codegen/klend/accounts';

/** Lending market obligation state */
export class ObligationZP {
  /** Version of the struct */
  readonly tag: BN
  /** Last update to collateral, liquidity, or their market values */
  readonly lastUpdate: types.LastUpdate
  /** Lending market address */
  readonly lendingMarket: Address
  /** Owner authority which can borrow liquidity */
  readonly owner: Address
  /** Deposited collateral for the obligation, unique by deposit reserve address */
  readonly deposits: Array<types.ObligationCollateral>
  /** Worst LTV for the collaterals backing the loan, represented as a percentage */
  readonly lowestReserveDepositLiquidationLtv: BN
  /** Market value of deposits (scaled fraction) */
  readonly depositedValueSf: BN
  /** Borrowed liquidity for the obligation, unique by borrow reserve address */
  readonly borrows: Array<types.ObligationLiquidity>
  /** Risk adjusted market value of borrows/debt (sum of price * borrowed_amount * borrow_factor) (scaled fraction) */
  readonly borrowFactorAdjustedDebtValueSf: BN
  /** Market value of borrows - used for max_liquidatable_borrowed_amount (scaled fraction) */
  readonly borrowedAssetsMarketValueSf: BN
  /** The maximum borrow value at the weighted average loan to value ratio (scaled fraction) */
  readonly allowedBorrowValueSf: BN
  /** The dangerous borrow value at the weighted average liquidation threshold (scaled fraction) */
  readonly unhealthyBorrowValueSf: BN
  /** The asset tier of the deposits */
  readonly depositsAssetTiers: Array<number>
  /** The asset tier of the borrows */
  readonly borrowsAssetTiers: Array<number>
  /** The elevation group id the obligation opted into. */
  readonly elevationGroup: number
  /** The number of obsolete reserves the obligation has a deposit in */
  readonly numOfObsoleteDepositReserves: number
  /** Marked = 1 if borrows array is not empty, 0 = borrows empty */
  readonly hasDebt: number
  /** Wallet address of the referrer */
  readonly referrer: Address
  /** Marked = 1 if borrowing disabled, 0 = borrowing enabled */
  readonly borrowingDisabled: number
  /**
   * A target LTV set by the risk council when marking this obligation for deleveraging.
   * Only effective when `deleveraging_margin_call_started_slot != 0`.
   */
  readonly autodeleverageTargetLtvPct: number
  /** The lowest max LTV found amongst the collateral deposits */
  readonly lowestReserveDepositMaxLtvPct: number
  /** The number of obsolete reserves the obligation has a borrow in */
  readonly numOfObsoleteBorrowReserves: number
  readonly reserved: Array<number>
  readonly highestBorrowFactorPct: BN
  /**
   * A timestamp at which the risk council most-recently marked this obligation for deleveraging.
   * Zero if not currently subject to deleveraging.
   */
  readonly autodeleverageMarginCallStartedTimestamp: BN
  /**
   * Owner-defined, liquidator-executed orders applicable to this obligation.
   * Typical use-cases would be a stop-loss and a take-profit (possibly co-existing).
   */
  readonly orders: Array<types.ObligationOrder>
  readonly padding3: Array<BN> = new Array(0)

  static readonly layout = borsh.struct<ObligationZP>([
    borsh.u64("tag"),
    types.LastUpdate.layout("lastUpdate"),
    borshAddress("lendingMarket"),
    borshAddress("owner"),
    borsh.array(types.ObligationCollateral.layout(), 8, "deposits"),
    borsh.u64("lowestReserveDepositLiquidationLtv"),
    borsh.u128("depositedValueSf"),
    borsh.array(types.ObligationLiquidity.layout(), 5, "borrows"),
    borsh.u128("borrowFactorAdjustedDebtValueSf"),
    borsh.u128("borrowedAssetsMarketValueSf"),
    borsh.u128("allowedBorrowValueSf"),
    borsh.u128("unhealthyBorrowValueSf"),
    borsh.array(borsh.u8(), 8, "depositsAssetTiers"),
    borsh.array(borsh.u8(), 5, "borrowsAssetTiers"),
    borsh.u8("elevationGroup"),
    borsh.u8("numOfObsoleteDepositReserves"),
    borsh.u8("hasDebt"),
    borshAddress("referrer"),
    borsh.u8("borrowingDisabled"),
    borsh.u8("autodeleverageTargetLtvPct"),
    borsh.u8("lowestReserveDepositMaxLtvPct"),
    borsh.u8("numOfObsoleteBorrowReserves"),
    borsh.array(borsh.u8(), 4, "reserved"),
    borsh.u64("highestBorrowFactorPct"),
    borsh.u64("autodeleverageMarginCallStartedTimestamp"),
    borsh.array(types.ObligationOrder.layout(), 2, "orders"),
  ])

  constructor(fields: ObligationFields) {
    this.tag = fields.tag
    this.lastUpdate = new types.LastUpdate({ ...fields.lastUpdate })
    this.lendingMarket = fields.lendingMarket
    this.owner = fields.owner
    this.deposits = fields.deposits.map(
      (item) => new types.ObligationCollateral({ ...item })
    )
    this.lowestReserveDepositLiquidationLtv =
      fields.lowestReserveDepositLiquidationLtv
    this.depositedValueSf = fields.depositedValueSf
    this.borrows = fields.borrows.map(
      (item) => new types.ObligationLiquidity({ ...item })
    )
    this.borrowFactorAdjustedDebtValueSf =
      fields.borrowFactorAdjustedDebtValueSf
    this.borrowedAssetsMarketValueSf = fields.borrowedAssetsMarketValueSf
    this.allowedBorrowValueSf = fields.allowedBorrowValueSf
    this.unhealthyBorrowValueSf = fields.unhealthyBorrowValueSf
    this.depositsAssetTiers = fields.depositsAssetTiers
    this.borrowsAssetTiers = fields.borrowsAssetTiers
    this.elevationGroup = fields.elevationGroup
    this.numOfObsoleteDepositReserves = fields.numOfObsoleteDepositReserves
    this.hasDebt = fields.hasDebt
    this.referrer = fields.referrer
    this.borrowingDisabled = fields.borrowingDisabled
    this.autodeleverageTargetLtvPct = fields.autodeleverageTargetLtvPct
    this.lowestReserveDepositMaxLtvPct = fields.lowestReserveDepositMaxLtvPct
    this.numOfObsoleteBorrowReserves = fields.numOfObsoleteBorrowReserves
    this.reserved = new Array<number>(0)
    this.highestBorrowFactorPct = fields.highestBorrowFactorPct
    this.autodeleverageMarginCallStartedTimestamp =
      fields.autodeleverageMarginCallStartedTimestamp
    this.orders = fields.orders.map(
      (item) => new types.ObligationOrder({ ...item })
    )
    this.padding3 = new Array<BN>(0);
  }

  static async fetch(
    rpc: Rpc<GetAccountInfoApi>,
    address: Address,
    programId: Address = PROGRAM_ID
  ): Promise<Obligation | null> {
    const info = await fetchEncodedAccount(rpc, address)

    if (!info.exists) {
      return null
    }
    if (info.programAddress !== programId) {
      throw new Error("account doesn't belong to this program")
    }

    return this.decode(Buffer.from(info.data))
  }

  static async fetchMultiple(
    rpc: Rpc<GetMultipleAccountsApi>,
    addresses: Address[],
    programId: Address = PROGRAM_ID
  ): Promise<Array<Obligation | null>> {
    const infos = await fetchEncodedAccounts(rpc, addresses)

    return infos.map((info) => {
      if (!info.exists) {
        return null
      }
      if (info.programAddress !== programId) {
        throw new Error("account doesn't belong to this program")
      }

      return this.decode(Buffer.from(info.data))
    })
  }

  static decode(data: Buffer): Obligation {
    if (!data.slice(0, 8).equals(Obligation.discriminator)) {
      throw new Error("invalid account discriminator")
    }

    const dec = ObligationZP.layout.decode(data.slice(8))

    return new Obligation({
      tag: dec.tag,
      lastUpdate: types.LastUpdate.fromDecoded(dec.lastUpdate),
      lendingMarket: dec.lendingMarket,
      owner: dec.owner,
      deposits: dec.deposits.map(
        (
          item: any /* eslint-disable-line @typescript-eslint/no-explicit-any */
        ) => types.ObligationCollateral.fromDecoded(item)
      ),
      lowestReserveDepositLiquidationLtv:
        dec.lowestReserveDepositLiquidationLtv,
      depositedValueSf: dec.depositedValueSf,
      borrows: dec.borrows.map(
        (
          item: any /* eslint-disable-line @typescript-eslint/no-explicit-any */
        ) => types.ObligationLiquidity.fromDecoded(item)
      ),
      borrowFactorAdjustedDebtValueSf: dec.borrowFactorAdjustedDebtValueSf,
      borrowedAssetsMarketValueSf: dec.borrowedAssetsMarketValueSf,
      allowedBorrowValueSf: dec.allowedBorrowValueSf,
      unhealthyBorrowValueSf: dec.unhealthyBorrowValueSf,
      depositsAssetTiers: dec.depositsAssetTiers,
      borrowsAssetTiers: dec.borrowsAssetTiers,
      elevationGroup: dec.elevationGroup,
      numOfObsoleteDepositReserves: dec.numOfObsoleteDepositReserves,
      hasDebt: dec.hasDebt,
      referrer: dec.referrer,
      borrowingDisabled: dec.borrowingDisabled,
      autodeleverageTargetLtvPct: dec.autodeleverageTargetLtvPct,
      lowestReserveDepositMaxLtvPct: dec.lowestReserveDepositMaxLtvPct,
      numOfObsoleteBorrowReserves: dec.numOfObsoleteBorrowReserves,
      reserved: dec.reserved,
      highestBorrowFactorPct: dec.highestBorrowFactorPct,
      autodeleverageMarginCallStartedTimestamp:
        dec.autodeleverageMarginCallStartedTimestamp,
      orders: dec.orders.map(
        (
          item: any /* eslint-disable-line @typescript-eslint/no-explicit-any */
        ) => types.ObligationOrder.fromDecoded(item)
      ),
      padding3: [],
    })
  }
}
