import type { BN } from '@project-serum/anchor';
import { AnchorProvider, Program } from '@project-serum/anchor';
import type { Wallet } from '@saberhq/solana-contrib';
import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
import type {
  AccountMeta,
  Connection,
  PublicKey,
  Transaction
} from '@solana/web3.js';
import {
  SystemProgram,
  SYSVAR_RECENT_BLOCKHASHES_PUBKEY
} from '@solana/web3.js';

import type { BLAZE_PROGRAM } from './constants';
import { BLAZE_ADDRESS, BLAZE_IDL } from './constants';

export const initIdentifierInstruction = (
  connection: Connection,
  wallet: Wallet,
  params: {
    identifierId: PublicKey;
  }
): Promise<Transaction> => {
  const provider = new AnchorProvider(connection, wallet, {});
  const blazeProgram = new Program<BLAZE_PROGRAM>(
    BLAZE_IDL,
    BLAZE_ADDRESS,
    provider
  );
  return blazeProgram.methods
    .initIdentifier()
    .accounts({
      identifier: params.identifierId,
      payer: wallet.publicKey,
      systemProgram: SystemProgram.programId
    })
    .transaction();
};

export const initBlazeInstruction = (
  connection: Connection,
  wallet: Wallet,
  params: {
    identifierId: PublicKey;
    blazeId: PublicKey;

    projectId: PublicKey;
    projectWallet: PublicKey;

    blazePrizeMintTokenAccount: PublicKey;
    userPrizeMintTokenAccount: PublicKey;
    entrants: PublicKey;

    tokenMint?: PublicKey;
    tokenAccount?: PublicKey;
    treasury?: PublicKey;
    burnRate?: number;

    category?: string;
    prizeMint: PublicKey;
    entrantFee: BN;
    maxEntrants: number;
    start: BN;
    end: BN;
    maxEntrantsPerWalletRate?: number;
  }
): Promise<Transaction> => {
  const provider = new AnchorProvider(connection, wallet, {});
  const blazeProgram = new Program<BLAZE_PROGRAM>(
    BLAZE_IDL,
    BLAZE_ADDRESS,
    provider
  );
  return blazeProgram.methods
    .initBlaze({
      prizeMint: params.prizeMint,
      tokenMint: params.tokenMint || null,
      tokenAccount: params.tokenAccount || null,
      treasury: params.treasury || null,
      entrantFee: params.entrantFee,
      maxEntrants: params.maxEntrants,
      start: params.start,
      end: params.end,
      maxEntrantsPerWalletRate: params.maxEntrantsPerWalletRate || null,
      category: params.category || null,
      burnRate: params.burnRate || null
    })
    .accounts({
      blaze: params.blazeId,
      identifier: params.identifierId,
      project: params.projectId,
      projectWallet: params.projectWallet,
      blazePrizeMintTokenAccount: params.blazePrizeMintTokenAccount,
      authority: wallet.publicKey,
      authorityPrizeMintTokenAccount: params.userPrizeMintTokenAccount,
      entrants: params.entrants,
      systemProgram: SystemProgram.programId,
      tokenProgram: TOKEN_PROGRAM_ID
    })
    .transaction();
};

export const updateBlazeInstruction = (
  connection: Connection,
  wallet: Wallet,
  params: {
    blazeId: PublicKey;
    projectId: PublicKey;

    tokenMint?: PublicKey;
    tokenAccount?: PublicKey;
    treasury?: PublicKey;
    burnRate?: number;

    entrantFee: BN;
    start: BN;
    end: BN;
    maxEntrantsPerWalletRate?: number;
    category?: string;
  }
): Promise<Transaction> => {
  const provider = new AnchorProvider(connection, wallet, {});
  const blazeProgram = new Program<BLAZE_PROGRAM>(
    BLAZE_IDL,
    BLAZE_ADDRESS,
    provider
  );
  return blazeProgram.methods
    .updateBlaze({
      tokenMint: params.tokenMint || null,
      tokenAccount: params.tokenAccount || null,
      treasury: params.treasury || null,
      entrantFee: params.entrantFee,
      start: params.start,
      end: params.end,
      maxEntrantsPerWalletRate: params.maxEntrantsPerWalletRate || null,
      category: params.category || null,
      burnRate: params.burnRate || null
    })
    .accounts({
      blaze: params.blazeId,
      project: params.projectId,
      authority: wallet.publicKey
    })
    .transaction();
};

export const closeBlazeInstruction = (
  connection: Connection,
  wallet: Wallet,
  params: {
    blazeId: PublicKey;
    projectId: PublicKey;
    blazePrizeMintTokenAccount: PublicKey;
    userPrizeMintTokenAccount: PublicKey;
    entrants: PublicKey;
  }
): Promise<Transaction> => {
  const provider = new AnchorProvider(connection, wallet, {});
  const blazeProgram = new Program<BLAZE_PROGRAM>(
    BLAZE_IDL,
    BLAZE_ADDRESS,
    provider
  );
  return blazeProgram.methods
    .closeBlaze()
    .accounts({
      blaze: params.blazeId,
      project: params.projectId,
      blazePrizeMintTokenAccount: params.blazePrizeMintTokenAccount,
      authorityPrizeMintTokenAccount: params.userPrizeMintTokenAccount,
      entrants: params.entrants,
      authority: wallet.publicKey,
      tokenProgram: TOKEN_PROGRAM_ID
    })
    .transaction();
};

export const redeemEntrantsInstruction = (
  connection: Connection,
  wallet: Wallet,
  params: {
    quantity: number;
    blazeId: PublicKey;
    entrants: PublicKey;
    remainingAccountsForRedeem: AccountMeta[];
  }
): Promise<Transaction> => {
  const provider = new AnchorProvider(connection, wallet, {});
  const blazeProgram = new Program<BLAZE_PROGRAM>(
    BLAZE_IDL,
    BLAZE_ADDRESS,
    provider
  );
  return blazeProgram.methods
    .redeemEntrants(params.quantity)
    .accounts({
      blaze: params.blazeId,
      entrants: params.entrants,
      signer: wallet.publicKey,
      systemProgram: SystemProgram.programId,
      tokenProgram: TOKEN_PROGRAM_ID
    })
    .remainingAccounts(params.remainingAccountsForRedeem)
    .transaction();
};

export const resolveBlazeInstruction = (
  connection: Connection,
  wallet: Wallet,
  params: {
    blazeId: PublicKey;
    entrants: PublicKey;
  }
): Promise<Transaction> => {
  const provider = new AnchorProvider(connection, wallet, {});
  const blazeProgram = new Program<BLAZE_PROGRAM>(
    BLAZE_IDL,
    BLAZE_ADDRESS,
    provider
  );
  return blazeProgram.methods
    .resolveBlaze()
    .accounts({
      blaze: params.blazeId,
      entrants: params.entrants,
      recentBlockhashes: SYSVAR_RECENT_BLOCKHASHES_PUBKEY,
      tokenProgram: TOKEN_PROGRAM_ID
    })
    .transaction();
};

export const claimPrizeInstruction = (
  connection: Connection,
  wallet: Wallet,
  params: {
    blazeId: PublicKey;
    blazePrizeMintTokenAccount: PublicKey;
    winnerPrizeMintTokenAccount: PublicKey;
  }
): Promise<Transaction> => {
  const provider = new AnchorProvider(connection, wallet, {});
  const blazeProgram = new Program<BLAZE_PROGRAM>(
    BLAZE_IDL,
    BLAZE_ADDRESS,
    provider
  );
  return blazeProgram.methods
    .claimPrize()
    .accounts({
      blaze: params.blazeId,
      blazePrizeMintTokenAccount: params.blazePrizeMintTokenAccount,
      winnerPrizeMintTokenAccount: params.winnerPrizeMintTokenAccount,
      winner: wallet.publicKey,
      tokenProgram: TOKEN_PROGRAM_ID
    })
    .transaction();
};
