import {
  AccountInfo,
  Connection,
  PublicKey,
  TransactionInstruction,
} from "@solana/web3.js";
import { depositLpToken } from "./deposit";
import {
  CollectCoinCreatorFeeSolanaState,
  CreatePoolSolanaState,
  DepositBaseAndLpTokenFromQuoteResult,
  DepositQuoteAndLpTokenFromBaseResult,
  GlobalConfig,
  GlobalVolumeAccumulator,
  LiquiditySolanaState,
  SwapSolanaState,
  UserVolumeAccumulator,
  WithdrawAutocompleteResult,
} from "../types/sdk";
import { PumpAmmInternalSdk } from "./pumpAmmInternal";
import { PUMP_AMM_PROGRAM_ID } from "./pda";
import BN from "bn.js";

export class PumpAmmSdk {
  private readonly pumpAmmInternalSdk: PumpAmmInternalSdk;

  constructor(connection: Connection, programId: string = PUMP_AMM_PROGRAM_ID) {
    this.pumpAmmInternalSdk = new PumpAmmInternalSdk(connection, programId);
  }

  programId(): PublicKey {
    return this.pumpAmmInternalSdk.programId();
  }

  globalConfigKey(): PublicKey {
    return this.pumpAmmInternalSdk.globalConfigKey();
  }

  poolKey(
    index: number,
    creator: PublicKey,
    baseMint: PublicKey,
    quoteMint: PublicKey,
  ): [PublicKey, number] {
    return this.pumpAmmInternalSdk.poolKey(index, creator, baseMint, quoteMint);
  }

  lpMintKey(pool: PublicKey): [PublicKey, number] {
    return this.pumpAmmInternalSdk.lpMintKey(pool);
  }

  fetchGlobalConfigAccount() {
    return this.pumpAmmInternalSdk.fetchGlobalConfigAccount();
  }

  fetchPool(pool: PublicKey) {
    return this.pumpAmmInternalSdk.fetchPool(pool);
  }

  decodeGlobalConfig(
    globalConfigAccountInfo: AccountInfo<Buffer>,
  ): GlobalConfig {
    return this.pumpAmmInternalSdk.decodeGlobalConfig(globalConfigAccountInfo);
  }

  decodePool(poolAccountInfo: AccountInfo<Buffer>) {
    return this.pumpAmmInternalSdk.decodePool(poolAccountInfo);
  }

  fetchGlobalVolumeAccumulator(): Promise<GlobalVolumeAccumulator> {
    return this.pumpAmmInternalSdk.fetchGlobalVolumeAccumulator();
  }

  decodeGlobalVolumeAccumulator(
    globalVolumeAccumulatorAccountInfo: AccountInfo<Buffer>,
  ): GlobalVolumeAccumulator {
    return this.pumpAmmInternalSdk.decodeGlobalVolumeAccumulator(
      globalVolumeAccumulatorAccountInfo,
    );
  }

  fetchUserVolumeAccumulator(
    user: PublicKey,
  ): Promise<UserVolumeAccumulator | null> {
    return this.pumpAmmInternalSdk.fetchUserVolumeAccumulator(user);
  }

  decodeUserVolumeAccumulator(
    userVolumeAccumulatorAccountInfo: AccountInfo<Buffer>,
  ): UserVolumeAccumulator {
    return this.pumpAmmInternalSdk.decodeUserVolumeAccumulator(
      userVolumeAccumulatorAccountInfo,
    );
  }

  async createPoolInstructions(
    createPoolSolanaState: CreatePoolSolanaState,
    baseIn: BN,
    quoteIn: BN,
  ): Promise<TransactionInstruction[]> {
    return this.pumpAmmInternalSdk.createPoolInstructionsInternal(
      createPoolSolanaState,
      baseIn,
      quoteIn,
    );
  }

  async createAutocompleteInitialPoolPrice(
    initialBase: BN,
    initialQuote: BN,
  ): Promise<BN> {
    return initialQuote.div(initialBase);
  }

  async depositInstructions(
    liquiditySolanaState: LiquiditySolanaState,
    lpToken: BN,
    slippage: number,
  ): Promise<TransactionInstruction[]> {
    const { pool, poolBaseTokenAccount, poolQuoteTokenAccount } =
      liquiditySolanaState;

    const { maxBase, maxQuote } = depositLpToken(
      lpToken,
      slippage,
      new BN(poolBaseTokenAccount.amount.toString()),
      new BN(poolQuoteTokenAccount.amount.toString()),
      pool.lpSupply,
    );

    return this.pumpAmmInternalSdk.depositInstructionsInternal(
      liquiditySolanaState,
      lpToken,
      maxBase,
      maxQuote,
    );
  }

  depositAutocompleteQuoteAndLpTokenFromBase(
    liquiditySolanaState: LiquiditySolanaState,
    base: BN,
    slippage: number,
  ): DepositQuoteAndLpTokenFromBaseResult {
    const { quote, lpToken } = this.pumpAmmInternalSdk.depositBaseInputInternal(
      liquiditySolanaState,
      base,
      slippage,
    );

    return {
      quote,
      lpToken,
    };
  }

  depositAutocompleteBaseAndLpTokenFromQuote(
    liquiditySolanaState: LiquiditySolanaState,
    quote: BN,
    slippage: number,
  ): DepositBaseAndLpTokenFromQuoteResult {
    const { base, lpToken } = this.pumpAmmInternalSdk.depositQuoteInputInternal(
      liquiditySolanaState,
      quote,
      slippage,
    );

    return {
      base,
      lpToken,
    };
  }

  async withdrawInstructions(
    liquiditySolanaState: LiquiditySolanaState,
    lpToken: BN,
    slippage: number,
  ): Promise<TransactionInstruction[]> {
    const { minBase, minQuote } =
      this.pumpAmmInternalSdk.withdrawInputsInternal(
        liquiditySolanaState,
        lpToken,
        slippage,
      );

    return this.pumpAmmInternalSdk.withdrawInstructionsInternal(
      liquiditySolanaState,
      lpToken,
      minBase,
      minQuote,
    );
  }

  withdrawAutoCompleteBaseAndQuoteFromLpToken(
    liquiditySolanaState: LiquiditySolanaState,
    lpAmount: BN,
    slippage: number,
  ): WithdrawAutocompleteResult {
    const { base, quote } = this.pumpAmmInternalSdk.withdrawInputsInternal(
      liquiditySolanaState,
      lpAmount,
      slippage,
    );

    return {
      base,
      quote,
    };
  }

  async extendAccount(
    account: PublicKey,
    user: PublicKey,
  ): Promise<TransactionInstruction> {
    return this.pumpAmmInternalSdk.extendAccount(account, user);
  }

  async collectCoinCreatorFee(
    collectCoinCreatorFeeSolanaState: CollectCoinCreatorFeeSolanaState,
  ): Promise<TransactionInstruction[]> {
    return this.pumpAmmInternalSdk.collectCoinCreatorFee(
      collectCoinCreatorFeeSolanaState,
    );
  }

  async getCoinCreatorVaultBalance(coinCreator: PublicKey): Promise<BN> {
    return this.pumpAmmInternalSdk.getCoinCreatorVaultBalance(coinCreator);
  }

  coinCreatorVaultAuthorityPda(coinCreator: PublicKey) {
    return this.pumpAmmInternalSdk.coinCreatorVaultAuthorityPda(coinCreator);
  }

  coinCreatorVaultAta(
    coinCreatorVaultAuthority: PublicKey,
    quoteMint: PublicKey,
    quoteTokenProgram: PublicKey,
  ) {
    return this.pumpAmmInternalSdk.coinCreatorVaultAta(
      coinCreatorVaultAuthority,
      quoteMint,
      quoteTokenProgram,
    );
  }

  async setCoinCreator(pool: PublicKey): Promise<TransactionInstruction> {
    return this.pumpAmmInternalSdk.setCoinCreator(pool);
  }

  async createPoolSolanaState(
    index: number,
    creator: PublicKey,
    baseMint: PublicKey,
    quoteMint: PublicKey,
    userBaseTokenAccount: PublicKey | undefined = undefined,
    userQuoteTokenAccount: PublicKey | undefined = undefined,
  ): Promise<CreatePoolSolanaState> {
    return this.pumpAmmInternalSdk.createPoolSolanaState(
      index,
      creator,
      baseMint,
      quoteMint,
      userBaseTokenAccount,
      userQuoteTokenAccount,
    );
  }

  async swapSolanaState(
    poolKey: PublicKey,
    user: PublicKey,
    userBaseTokenAccount: PublicKey | undefined = undefined,
    userQuoteTokenAccount: PublicKey | undefined = undefined,
  ): Promise<SwapSolanaState> {
    return this.pumpAmmInternalSdk.swapSolanaState(
      poolKey,
      user,
      userBaseTokenAccount,
      userQuoteTokenAccount,
    );
  }

  async liquiditySolanaState(
    poolKey: PublicKey,
    user: PublicKey,
    userBaseTokenAccount: PublicKey | undefined = undefined,
    userQuoteTokenAccount: PublicKey | undefined = undefined,
    userPoolTokenAccount: PublicKey | undefined = undefined,
  ): Promise<LiquiditySolanaState> {
    return this.pumpAmmInternalSdk.liquiditySolanaState(
      poolKey,
      user,
      userBaseTokenAccount,
      userQuoteTokenAccount,
      userPoolTokenAccount,
    );
  }

  async collectCoinCreatorFeeSolanaState(
    coinCreator: PublicKey,
    coinCreatorTokenAccount: PublicKey | undefined = undefined,
  ): Promise<CollectCoinCreatorFeeSolanaState> {
    return this.pumpAmmInternalSdk.collectCoinCreatorFeeSolanaState(
      coinCreator,
      coinCreatorTokenAccount,
    );
  }

  async claimTokenIncentives(
    user: PublicKey,
    payer: PublicKey,
  ): Promise<TransactionInstruction[]> {
    return this.pumpAmmInternalSdk.claimTokenIncentivesInternal(user, payer);
  }

  async getTotalUnclaimedTokens(user: PublicKey): Promise<BN> {
    return this.pumpAmmInternalSdk.getTotalUnclaimedTokens(user);
  }

  async getCurrentDayTokens(user: PublicKey): Promise<BN> {
    return this.pumpAmmInternalSdk.getCurrentDayTokens(user);
  }

  async syncUserVolumeAccumulator(
    user: PublicKey,
  ): Promise<TransactionInstruction> {
    return this.pumpAmmInternalSdk.syncUserVolumeAccumulator(user);
  }

  async initUserVolumeAccumulator({
    payer,
    user,
  }: {
    payer: PublicKey;
    user: PublicKey;
  }): Promise<TransactionInstruction> {
    return await this.pumpAmmInternalSdk.initUserVolumeAccumulator({
      payer,
      user,
    });
  }

  async closeUserVolumeAccumulator(
    user: PublicKey,
  ): Promise<TransactionInstruction> {
    return await this.pumpAmmInternalSdk.closeUserVolumeAccumulator(user);
  }

  async buyBaseInput(
    swapSolanaState: SwapSolanaState,
    base: BN,
    slippage: number,
  ): Promise<TransactionInstruction[]> {
    return this.pumpAmmInternalSdk.buyBaseInput(
      swapSolanaState,
      base,
      slippage,
    );
  }

  async buyQuoteInput(
    swapSolanaState: SwapSolanaState,
    quote: BN,
    slippage: number,
  ): Promise<TransactionInstruction[]> {
    return this.pumpAmmInternalSdk.buyQuoteInput(
      swapSolanaState,
      quote,
      slippage,
    );
  }

  async sellBaseInput(
    swapSolanaState: SwapSolanaState,
    base: BN,
    slippage: number,
  ): Promise<TransactionInstruction[]> {
    return this.pumpAmmInternalSdk.sellBaseInput(
      swapSolanaState,
      base,
      slippage,
    );
  }

  async sellQuoteInput(
    swapSolanaState: SwapSolanaState,
    quote: BN,
    slippage: number,
  ): Promise<TransactionInstruction[]> {
    return this.pumpAmmInternalSdk.sellQuoteInput(
      swapSolanaState,
      quote,
      slippage,
    );
  }
}
