/* 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 "../types" // eslint-disable-line @typescript-eslint/no-unused-vars
import { PROGRAM_ID } from "../programId"

export interface AggregatorAccountDataFields {
  name: Array<number>
  metadata: Array<number>
  reserved1: Array<number>
  queuePubkey: Address
  oracleRequestBatchSize: number
  minOracleResults: number
  minJobResults: number
  minUpdateDelaySeconds: number
  startAfter: BN
  varianceThreshold: types.SwitchboardDecimalFields
  forceReportPeriod: BN
  expiration: BN
  consecutiveFailureCount: BN
  nextAllowedUpdateTime: BN
  isLocked: boolean
  crankPubkey: Address
  latestConfirmedRound: types.AggregatorRoundFields
  currentRound: types.AggregatorRoundFields
  jobPubkeysData: Array<Address>
  jobHashes: Array<types.HashFields>
  jobPubkeysSize: number
  jobsChecksum: Array<number>
  authority: Address
  historyBuffer: Address
  previousConfirmedRoundResult: types.SwitchboardDecimalFields
  previousConfirmedRoundSlot: BN
  disableCrank: boolean
  jobWeights: Array<number>
  creationTimestamp: BN
  resolutionMode: types.AggregatorResolutionModeKind
  basePriorityFee: number
  priorityFeeBump: number
  priorityFeeBumpPeriod: number
  maxPriorityFeeMultiplier: number
  ebuf: Array<number>
}

export interface AggregatorAccountDataJSON {
  name: Array<number>
  metadata: Array<number>
  reserved1: Array<number>
  queuePubkey: string
  oracleRequestBatchSize: number
  minOracleResults: number
  minJobResults: number
  minUpdateDelaySeconds: number
  startAfter: string
  varianceThreshold: types.SwitchboardDecimalJSON
  forceReportPeriod: string
  expiration: string
  consecutiveFailureCount: string
  nextAllowedUpdateTime: string
  isLocked: boolean
  crankPubkey: string
  latestConfirmedRound: types.AggregatorRoundJSON
  currentRound: types.AggregatorRoundJSON
  jobPubkeysData: Array<string>
  jobHashes: Array<types.HashJSON>
  jobPubkeysSize: number
  jobsChecksum: Array<number>
  authority: string
  historyBuffer: string
  previousConfirmedRoundResult: types.SwitchboardDecimalJSON
  previousConfirmedRoundSlot: string
  disableCrank: boolean
  jobWeights: Array<number>
  creationTimestamp: string
  resolutionMode: types.AggregatorResolutionModeJSON
  basePriorityFee: number
  priorityFeeBump: number
  priorityFeeBumpPeriod: number
  maxPriorityFeeMultiplier: number
  ebuf: Array<number>
}

export class AggregatorAccountData {
  readonly name: Array<number>
  readonly metadata: Array<number>
  readonly reserved1: Array<number>
  readonly queuePubkey: Address
  readonly oracleRequestBatchSize: number
  readonly minOracleResults: number
  readonly minJobResults: number
  readonly minUpdateDelaySeconds: number
  readonly startAfter: BN
  readonly varianceThreshold: types.SwitchboardDecimal
  readonly forceReportPeriod: BN
  readonly expiration: BN
  readonly consecutiveFailureCount: BN
  readonly nextAllowedUpdateTime: BN
  readonly isLocked: boolean
  readonly crankPubkey: Address
  readonly latestConfirmedRound: types.AggregatorRound
  readonly currentRound: types.AggregatorRound
  readonly jobPubkeysData: Array<Address>
  readonly jobHashes: Array<types.Hash>
  readonly jobPubkeysSize: number
  readonly jobsChecksum: Array<number>
  readonly authority: Address
  readonly historyBuffer: Address
  readonly previousConfirmedRoundResult: types.SwitchboardDecimal
  readonly previousConfirmedRoundSlot: BN
  readonly disableCrank: boolean
  readonly jobWeights: Array<number>
  readonly creationTimestamp: BN
  readonly resolutionMode: types.AggregatorResolutionModeKind
  readonly basePriorityFee: number
  readonly priorityFeeBump: number
  readonly priorityFeeBumpPeriod: number
  readonly maxPriorityFeeMultiplier: number
  readonly ebuf: Array<number>

  static readonly discriminator = Buffer.from([
    217, 230, 65, 101, 201, 162, 27, 125,
  ])

  static readonly layout = borsh.struct<AggregatorAccountData>([
    borsh.array(borsh.u8(), 32, "name"),
    borsh.array(borsh.u8(), 128, "metadata"),
    borsh.array(borsh.u8(), 32, "reserved1"),
    borshAddress("queuePubkey"),
    borsh.u32("oracleRequestBatchSize"),
    borsh.u32("minOracleResults"),
    borsh.u32("minJobResults"),
    borsh.u32("minUpdateDelaySeconds"),
    borsh.i64("startAfter"),
    types.SwitchboardDecimal.layout("varianceThreshold"),
    borsh.i64("forceReportPeriod"),
    borsh.i64("expiration"),
    borsh.u64("consecutiveFailureCount"),
    borsh.i64("nextAllowedUpdateTime"),
    borsh.bool("isLocked"),
    borshAddress("crankPubkey"),
    types.AggregatorRound.layout("latestConfirmedRound"),
    types.AggregatorRound.layout("currentRound"),
    borsh.array(borshAddress(), 16, "jobPubkeysData"),
    borsh.array(types.Hash.layout(), 16, "jobHashes"),
    borsh.u32("jobPubkeysSize"),
    borsh.array(borsh.u8(), 32, "jobsChecksum"),
    borshAddress("authority"),
    borshAddress("historyBuffer"),
    types.SwitchboardDecimal.layout("previousConfirmedRoundResult"),
    borsh.u64("previousConfirmedRoundSlot"),
    borsh.bool("disableCrank"),
    borsh.array(borsh.u8(), 16, "jobWeights"),
    borsh.i64("creationTimestamp"),
    types.AggregatorResolutionMode.layout("resolutionMode"),
    borsh.u32("basePriorityFee"),
    borsh.u32("priorityFeeBump"),
    borsh.u32("priorityFeeBumpPeriod"),
    borsh.u32("maxPriorityFeeMultiplier"),
    borsh.array(borsh.u8(), 122, "ebuf"),
  ])

  constructor(fields: AggregatorAccountDataFields) {
    this.name = fields.name
    this.metadata = fields.metadata
    this.reserved1 = fields.reserved1
    this.queuePubkey = fields.queuePubkey
    this.oracleRequestBatchSize = fields.oracleRequestBatchSize
    this.minOracleResults = fields.minOracleResults
    this.minJobResults = fields.minJobResults
    this.minUpdateDelaySeconds = fields.minUpdateDelaySeconds
    this.startAfter = fields.startAfter
    this.varianceThreshold = new types.SwitchboardDecimal({
      ...fields.varianceThreshold,
    })
    this.forceReportPeriod = fields.forceReportPeriod
    this.expiration = fields.expiration
    this.consecutiveFailureCount = fields.consecutiveFailureCount
    this.nextAllowedUpdateTime = fields.nextAllowedUpdateTime
    this.isLocked = fields.isLocked
    this.crankPubkey = fields.crankPubkey
    this.latestConfirmedRound = new types.AggregatorRound({
      ...fields.latestConfirmedRound,
    })
    this.currentRound = new types.AggregatorRound({ ...fields.currentRound })
    this.jobPubkeysData = fields.jobPubkeysData
    this.jobHashes = fields.jobHashes.map((item) => new types.Hash({ ...item }))
    this.jobPubkeysSize = fields.jobPubkeysSize
    this.jobsChecksum = fields.jobsChecksum
    this.authority = fields.authority
    this.historyBuffer = fields.historyBuffer
    this.previousConfirmedRoundResult = new types.SwitchboardDecimal({
      ...fields.previousConfirmedRoundResult,
    })
    this.previousConfirmedRoundSlot = fields.previousConfirmedRoundSlot
    this.disableCrank = fields.disableCrank
    this.jobWeights = fields.jobWeights
    this.creationTimestamp = fields.creationTimestamp
    this.resolutionMode = fields.resolutionMode
    this.basePriorityFee = fields.basePriorityFee
    this.priorityFeeBump = fields.priorityFeeBump
    this.priorityFeeBumpPeriod = fields.priorityFeeBumpPeriod
    this.maxPriorityFeeMultiplier = fields.maxPriorityFeeMultiplier
    this.ebuf = fields.ebuf
  }

  static async fetch(
    rpc: Rpc<GetAccountInfoApi>,
    address: Address,
    programId: Address = PROGRAM_ID
  ): Promise<AggregatorAccountData | 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<AggregatorAccountData | 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): AggregatorAccountData {
    if (!data.slice(0, 8).equals(AggregatorAccountData.discriminator)) {
      throw new Error("invalid account discriminator")
    }

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

    return new AggregatorAccountData({
      name: dec.name,
      metadata: dec.metadata,
      reserved1: dec.reserved1,
      queuePubkey: dec.queuePubkey,
      oracleRequestBatchSize: dec.oracleRequestBatchSize,
      minOracleResults: dec.minOracleResults,
      minJobResults: dec.minJobResults,
      minUpdateDelaySeconds: dec.minUpdateDelaySeconds,
      startAfter: dec.startAfter,
      varianceThreshold: types.SwitchboardDecimal.fromDecoded(
        dec.varianceThreshold
      ),
      forceReportPeriod: dec.forceReportPeriod,
      expiration: dec.expiration,
      consecutiveFailureCount: dec.consecutiveFailureCount,
      nextAllowedUpdateTime: dec.nextAllowedUpdateTime,
      isLocked: dec.isLocked,
      crankPubkey: dec.crankPubkey,
      latestConfirmedRound: types.AggregatorRound.fromDecoded(
        dec.latestConfirmedRound
      ),
      currentRound: types.AggregatorRound.fromDecoded(dec.currentRound),
      jobPubkeysData: dec.jobPubkeysData,
      jobHashes: dec.jobHashes.map(
        (
          item: any /* eslint-disable-line @typescript-eslint/no-explicit-any */
        ) => types.Hash.fromDecoded(item)
      ),
      jobPubkeysSize: dec.jobPubkeysSize,
      jobsChecksum: dec.jobsChecksum,
      authority: dec.authority,
      historyBuffer: dec.historyBuffer,
      previousConfirmedRoundResult: types.SwitchboardDecimal.fromDecoded(
        dec.previousConfirmedRoundResult
      ),
      previousConfirmedRoundSlot: dec.previousConfirmedRoundSlot,
      disableCrank: dec.disableCrank,
      jobWeights: dec.jobWeights,
      creationTimestamp: dec.creationTimestamp,
      resolutionMode: types.AggregatorResolutionMode.fromDecoded(
        dec.resolutionMode
      ),
      basePriorityFee: dec.basePriorityFee,
      priorityFeeBump: dec.priorityFeeBump,
      priorityFeeBumpPeriod: dec.priorityFeeBumpPeriod,
      maxPriorityFeeMultiplier: dec.maxPriorityFeeMultiplier,
      ebuf: dec.ebuf,
    })
  }

  toJSON(): AggregatorAccountDataJSON {
    return {
      name: this.name,
      metadata: this.metadata,
      reserved1: this.reserved1,
      queuePubkey: this.queuePubkey,
      oracleRequestBatchSize: this.oracleRequestBatchSize,
      minOracleResults: this.minOracleResults,
      minJobResults: this.minJobResults,
      minUpdateDelaySeconds: this.minUpdateDelaySeconds,
      startAfter: this.startAfter.toString(),
      varianceThreshold: this.varianceThreshold.toJSON(),
      forceReportPeriod: this.forceReportPeriod.toString(),
      expiration: this.expiration.toString(),
      consecutiveFailureCount: this.consecutiveFailureCount.toString(),
      nextAllowedUpdateTime: this.nextAllowedUpdateTime.toString(),
      isLocked: this.isLocked,
      crankPubkey: this.crankPubkey,
      latestConfirmedRound: this.latestConfirmedRound.toJSON(),
      currentRound: this.currentRound.toJSON(),
      jobPubkeysData: this.jobPubkeysData,
      jobHashes: this.jobHashes.map((item) => item.toJSON()),
      jobPubkeysSize: this.jobPubkeysSize,
      jobsChecksum: this.jobsChecksum,
      authority: this.authority,
      historyBuffer: this.historyBuffer,
      previousConfirmedRoundResult: this.previousConfirmedRoundResult.toJSON(),
      previousConfirmedRoundSlot: this.previousConfirmedRoundSlot.toString(),
      disableCrank: this.disableCrank,
      jobWeights: this.jobWeights,
      creationTimestamp: this.creationTimestamp.toString(),
      resolutionMode: this.resolutionMode.toJSON(),
      basePriorityFee: this.basePriorityFee,
      priorityFeeBump: this.priorityFeeBump,
      priorityFeeBumpPeriod: this.priorityFeeBumpPeriod,
      maxPriorityFeeMultiplier: this.maxPriorityFeeMultiplier,
      ebuf: this.ebuf,
    }
  }

  static fromJSON(obj: AggregatorAccountDataJSON): AggregatorAccountData {
    return new AggregatorAccountData({
      name: obj.name,
      metadata: obj.metadata,
      reserved1: obj.reserved1,
      queuePubkey: address(obj.queuePubkey),
      oracleRequestBatchSize: obj.oracleRequestBatchSize,
      minOracleResults: obj.minOracleResults,
      minJobResults: obj.minJobResults,
      minUpdateDelaySeconds: obj.minUpdateDelaySeconds,
      startAfter: new BN(obj.startAfter),
      varianceThreshold: types.SwitchboardDecimal.fromJSON(
        obj.varianceThreshold
      ),
      forceReportPeriod: new BN(obj.forceReportPeriod),
      expiration: new BN(obj.expiration),
      consecutiveFailureCount: new BN(obj.consecutiveFailureCount),
      nextAllowedUpdateTime: new BN(obj.nextAllowedUpdateTime),
      isLocked: obj.isLocked,
      crankPubkey: address(obj.crankPubkey),
      latestConfirmedRound: types.AggregatorRound.fromJSON(
        obj.latestConfirmedRound
      ),
      currentRound: types.AggregatorRound.fromJSON(obj.currentRound),
      jobPubkeysData: obj.jobPubkeysData.map((item) => address(item)),
      jobHashes: obj.jobHashes.map((item) => types.Hash.fromJSON(item)),
      jobPubkeysSize: obj.jobPubkeysSize,
      jobsChecksum: obj.jobsChecksum,
      authority: address(obj.authority),
      historyBuffer: address(obj.historyBuffer),
      previousConfirmedRoundResult: types.SwitchboardDecimal.fromJSON(
        obj.previousConfirmedRoundResult
      ),
      previousConfirmedRoundSlot: new BN(obj.previousConfirmedRoundSlot),
      disableCrank: obj.disableCrank,
      jobWeights: obj.jobWeights,
      creationTimestamp: new BN(obj.creationTimestamp),
      resolutionMode: types.AggregatorResolutionMode.fromJSON(
        obj.resolutionMode
      ),
      basePriorityFee: obj.basePriorityFee,
      priorityFeeBump: obj.priorityFeeBump,
      priorityFeeBumpPeriod: obj.priorityFeeBumpPeriod,
      maxPriorityFeeMultiplier: obj.maxPriorityFeeMultiplier,
      ebuf: obj.ebuf,
    })
  }
}
