import * as Instructions from "../rpc_client/instructions";
import * as anchor from "@coral-xyz/anchor";
import { PublicKey, Keypair } from "@solana/web3.js";

import * as Types from "../rpc_client/types";
import { getGlobalConfigValue } from "./utils";
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
import { TransactionInstruction } from "@solana/web3.js";
import { PROGRAM_ID } from "../rpc_client/programId";

export function initializeGlobalConfig(
  globalAdmin: PublicKey,
  globalConfig: PublicKey,
  treasuryVaultAuthority: PublicKey,
): TransactionInstruction {
  let accounts: Instructions.InitializeGlobalConfigAccounts = {
    globalAdmin,
    globalConfig: globalConfig,
    treasuryVaultsAuthority: treasuryVaultAuthority,
    systemProgram: anchor.web3.SystemProgram.programId,
  };

  let ix = Instructions.initializeGlobalConfig(accounts);

  return ix;
}

export function updateGlobalConfig(
  globalAdmin: PublicKey,
  globalConfig: PublicKey,
  mode: Types.GlobalConfigOptionKind,
  flagValue: string,
  flagValueType: string,
): TransactionInstruction {
  let formattedValue = getGlobalConfigValue(flagValueType, flagValue);

  let accounts: Instructions.UpdateGlobalConfigAccounts = {
    globalAdmin,
    globalConfig: globalConfig,
  };

  let args: Instructions.UpdateGlobalConfigArgs = {
    mode: mode.discriminator,
    value: formattedValue,
  };

  let ix = Instructions.updateGlobalConfig(args, accounts);

  return ix;
}

export function initializeFarm(
  globalConfig: PublicKey,
  farmAdmin: PublicKey,
  farmState: PublicKey,
  farmVault: PublicKey,
  farmVaultAuthority: PublicKey,
  tokenMint: PublicKey,
): TransactionInstruction {
  let accounts: Instructions.InitializeFarmAccounts = {
    farmAdmin,
    farmState: farmState,
    globalConfig: globalConfig,
    farmVault: farmVault,
    farmVaultsAuthority: farmVaultAuthority,
    tokenMint: tokenMint,
    tokenProgram: TOKEN_PROGRAM_ID,
    systemProgram: anchor.web3.SystemProgram.programId,
    rent: anchor.web3.SYSVAR_RENT_PUBKEY,
  };

  let ix = Instructions.initializeFarm(accounts);

  return ix;
}

export function initializeReward(
  globalConfig: PublicKey,
  treasuryVaultAuthority: PublicKey,
  treasuryVault: PublicKey,
  farmAdmin: PublicKey,
  farmState: PublicKey,
  rewardVault: PublicKey,
  farmVaultAuthority: PublicKey,
  rewardMint: PublicKey,
): TransactionInstruction {
  let accounts: Instructions.InitializeRewardAccounts = {
    farmAdmin,
    farmState: farmState,
    globalConfig: globalConfig,
    rewardVault: rewardVault,
    farmVaultsAuthority: farmVaultAuthority,
    treasuryVaultsAuthority: treasuryVaultAuthority,
    rewardTreasuryVault: treasuryVault,
    rewardMint: rewardMint,
    tokenProgram: TOKEN_PROGRAM_ID,
    systemProgram: anchor.web3.SystemProgram.programId,
    rent: anchor.web3.SYSVAR_RENT_PUBKEY,
  };

  let ix = Instructions.initializeReward(accounts);

  return ix;
}

export function addReward(
  farmAdmin: PublicKey,
  farmState: PublicKey,
  rewardVault: PublicKey,
  farmVaultAuthority: PublicKey,
  adminRewardAta: PublicKey,
  rewardMint: PublicKey,
  rewardIndex: number,
  amount: anchor.BN,
): TransactionInstruction {
  let accounts: Instructions.AddRewardsAccounts = {
    farmAdmin,
    farmState: farmState,
    rewardVault: rewardVault,
    farmVaultsAuthority: farmVaultAuthority,
    rewardTokenAta: adminRewardAta,
    rewardMint: rewardMint,
    tokenProgram: TOKEN_PROGRAM_ID,
    scopePrices: PROGRAM_ID,
  };

  let args: Instructions.AddRewardsArgs = {
    amount: amount,
    rewardIndex: new anchor.BN(rewardIndex),
  };

  let ix = Instructions.addRewards(args, accounts);

  return ix;
}

export function updateRewardConfig(
  farmAdmin: PublicKey,
  farmState: PublicKey,
  rewardIndex: number,
  mode: Types.FarmConfigOptionKind,
  value: number,
): TransactionInstruction {
  let accounts: Instructions.UpdateFarmConfigAccounts = {
    farmAdmin,
    farmState: farmState,
    scopePrices: PROGRAM_ID,
  };

  let data = serializeConfigValue(BigInt(rewardIndex), BigInt(value));

  let args: Instructions.UpdateFarmConfigArgs = {
    mode: mode.discriminator,
    data,
  };

  let ix = Instructions.updateFarmConfig(args, accounts);

  return ix;
}

export function refreshFarm(farmState: PublicKey): TransactionInstruction {
  let accounts: Instructions.RefreshFarmAccounts = {
    farmState: farmState,
    scopePrices: PROGRAM_ID,
  };

  let ix = Instructions.refreshFarm(accounts);

  return ix;
}

export function initializeUser(
  farmState: PublicKey,
  owner: PublicKey,
  userState: PublicKey,
): TransactionInstruction {
  let accounts: Instructions.InitializeUserAccounts = {
    authority: owner,
    payer: owner,
    delegatee: owner,
    owner,
    userState: userState,
    farmState: farmState,
    systemProgram: anchor.web3.SystemProgram.programId,
    rent: anchor.web3.SYSVAR_RENT_PUBKEY,
  };

  let ix = Instructions.initializeUser(accounts);

  return ix;
}

export function transferOwnership(
  owner: PublicKey,
  userState: PublicKey,
  newOwner: PublicKey,
): TransactionInstruction {
  let accounts: Instructions.TransferOwnershipAccounts = {
    owner: owner,
    userState: userState,
  };

  let args: Instructions.TransferOwnershipArgs = {
    newOwner,
  };

  let ix = Instructions.transferOwnership(args, accounts);

  return ix;
}

export function stake(
  owner: PublicKey,
  userState: PublicKey,
  ownerTokenAta: PublicKey,
  farmState: PublicKey,
  farmVault: PublicKey,
  tokenMint: PublicKey,
  amount: anchor.BN,
): TransactionInstruction {
  let accounts: Instructions.StakeAccounts = {
    owner: owner,
    userState: userState,
    farmState: farmState,
    farmVault: farmVault,
    userAta: ownerTokenAta,
    tokenMint: tokenMint,
    tokenProgram: TOKEN_PROGRAM_ID,
    scopePrices: PROGRAM_ID,
  };

  let args: Instructions.StakeArgs = {
    amount,
  };

  let ix = Instructions.stake(args, accounts);

  return ix;
}

export function unstake(
  owner: PublicKey,
  userState: PublicKey,
  farmState: PublicKey,
  amount: anchor.BN,
): TransactionInstruction {
  let accounts: Instructions.UnstakeAccounts = {
    owner: owner,
    userState: userState,
    farmState: farmState,
    scopePrices: PROGRAM_ID,
  };

  let args: Instructions.UnstakeArgs = {
    stakeSharesScaled: amount,
  };

  let ix = Instructions.unstake(args, accounts);

  return ix;
}

export function harvestReward(
  owner: PublicKey,
  userState: PublicKey,
  userRewardAta: PublicKey,
  globalConfig: PublicKey,
  treasuryVault: PublicKey,
  farmState: PublicKey,
  rewardVault: PublicKey,
  farmVaultAuthority: PublicKey,
  rewardIndex: number,
): TransactionInstruction {
  let accounts: Instructions.HarvestRewardAccounts = {
    owner: owner,
    userState: userState,
    farmState: farmState,
    globalConfig: globalConfig,
    rewardsVault: rewardVault,
    rewardsTreasuryVault: treasuryVault,
    userRewardAta: userRewardAta,
    farmVaultsAuthority: farmVaultAuthority,
    tokenProgram: TOKEN_PROGRAM_ID,
    scopePrices: PROGRAM_ID,
  };

  let args: Instructions.HarvestRewardArgs = {
    rewardIndex: new anchor.BN(rewardIndex),
  };

  let ix = Instructions.harvestReward(args, accounts);

  return ix;
}

export function withdrawTreasury(
  globalAdmin: PublicKey,
  globalConfig: PublicKey,
  treasuryVault: PublicKey,
  treasuryVaultAuthority: PublicKey,
  globalAdminWithdrawAta: PublicKey,
  amount: anchor.BN,
  rewardMint: PublicKey,
): TransactionInstruction {
  let accounts: Instructions.WithdrawTreasuryAccounts = {
    globalAdmin,
    globalConfig: globalConfig,
    rewardTreasuryVault: treasuryVault,
    treasuryVaultAuthority: treasuryVaultAuthority,
    withdrawDestinationTokenAccount: globalAdminWithdrawAta,
    rewardMint: rewardMint,
    tokenProgram: TOKEN_PROGRAM_ID,
  };

  let args: Instructions.WithdrawTreasuryArgs = {
    amount,
  };

  let ix = Instructions.withdrawTreasury(args, accounts);

  return ix;
}

export function refreshUserState(
  userState: PublicKey,
  farmState: PublicKey,
): TransactionInstruction {
  let accounts: Instructions.RefreshUserStateAccounts = {
    userState,
    farmState,
    scopePrices: PROGRAM_ID,
  };

  let ix = Instructions.refreshUserState(accounts);

  return ix;
}

export function withdrawUnstakedDeposit(
  owner: PublicKey,
  userState: PublicKey,
  farmState: PublicKey,
  userAta: PublicKey,
  farmVault: PublicKey,
  farmVaultsAuthority: PublicKey,
): TransactionInstruction {
  let accounts: Instructions.WithdrawUnstakedDepositsAccounts = {
    owner,
    userState,
    farmState,
    userAta,
    farmVault,
    farmVaultsAuthority,
    tokenProgram: TOKEN_PROGRAM_ID,
  };

  let ix = Instructions.withdrawUnstakedDeposits(accounts);

  return ix;
}

export function withdrawFromFarmVault(
  withdrawAuthority: PublicKey,
  farmState: PublicKey,
  withdrawerTokenAccount: PublicKey,
  farmVault: PublicKey,
  farmVaultsAuthority: PublicKey,
  amount: anchor.BN,
): TransactionInstruction {
  let accounts: Instructions.WithdrawFromFarmVaultAccounts = {
    farmState,
    withdrawAuthority,
    withdrawerTokenAccount,
    farmVault,
    farmVaultsAuthority,
    tokenProgram: TOKEN_PROGRAM_ID,
  };

  let args: Instructions.WithdrawFromFarmVaultArgs = {
    amount,
  };

  let ix = Instructions.withdrawFromFarmVault(args, accounts);

  return ix;
}

export function depositToFarmVault(
  depositor: PublicKey,
  farmState: PublicKey,
  farmVault: PublicKey,
  depositorAta: PublicKey,
  amount: anchor.BN,
): TransactionInstruction {
  let accounts: Instructions.DepositToFarmVaultAccounts = {
    depositor,
    farmState,
    farmVault,
    depositorAta,
    tokenProgram: TOKEN_PROGRAM_ID,
  };

  let args: Instructions.DepositToFarmVaultArgs = {
    amount,
  };

  let ix = Instructions.depositToFarmVault(args, accounts);

  return ix;
}

export function serializeConfigValue(
  reward_index: bigint,
  value: bigint,
): number[] {
  let buffer: Buffer;
  buffer = Buffer.alloc(32);
  buffer.writeBigUint64LE(reward_index, 0);
  buffer.writeBigUInt64LE(value, 8);
  return [...buffer];
}
