import { executeTransaction, getProject } from '@blockassetlabs/project';
import type { BN } from '@project-serum/anchor';
import type { Wallet } from '@saberhq/solana-contrib';
import type { Connection, PublicKey } from '@solana/web3.js';
import { Transaction } from '@solana/web3.js';

import {
  getBlaze,
  withClaimPrize,
  withCloseBlaze,
  withInitBlaze,
  withRedeemEntrants,
  withResolveBlaze,
  withUpdateBlaze
} from '../programs/blockasset-blaze';
import { withRemainingAccountsForRedeem } from '../programs/blockasset-blaze/utils';
import { withFindOrInitAssociatedTokenAccount } from '../utils/withFindOrInitAssociatedTokenAccount';

/**
 * Create blaze
 * @param connection
 * @param wallet
 * @param params
 * projectId: PublicKey;
 * prizeMint: PublicKey;
 * tokenMint?: PublicKey;
 * treasury: PublicKey;
 * burnRate?: number;
 * entrantFee: BN;
 * maxEntrants: number;
 * start: BN;
 * end: BN;
 * maxEntrantsPerWalletRate?: number;
 * category?: string;
 * @returns { signature, blazeId }
 */
export const createBlaze = async (
  connection: Connection,
  wallet: Wallet,
  params: {
    projectId: PublicKey;
    prizeMint: PublicKey;

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

    entrantFee: BN;
    maxEntrants: number;
    start: BN;
    end: BN;
    maxEntrantsPerWalletRate?: number;
    category?: string;
  }
): Promise<{ blazeId: PublicKey; signature: string }> => {
  if (params?.burnRate) {
    if (params.burnRate < 0 || params.burnRate > 100)
      throw new Error('Invalid burn rate');
    if (params.burnRate === 100 && params?.treasury)
      throw new Error('No need to set treasury with burnRate of 100');
    if (params.burnRate === 100 && params?.tokenMint)
      throw new Error('No need to set tokenMint with burnRate of 100');
  }
  if (!params.tokenMint && params.burnRate)
    throw new Error(
      'tokenMint needs to be provided with burn rates of greater than zero'
    );

  const transaction = new Transaction();

  const tokenAccount = params.tokenMint
    ? await withFindOrInitAssociatedTokenAccount(
        transaction,
        connection,
        params.tokenMint,
        params.treasury,
        wallet.publicKey
      )
    : undefined;

  const project = await getProject(connection, params.projectId);
  const [, blazeId, entrantsKeyPair] = await withInitBlaze(
    transaction,
    connection,
    wallet,
    {
      ...params,
      tokenAccount,
      treasury: tokenAccount ? undefined : params.treasury,
      projectWallet: project.parsed.treasuryAccount
    }
  );

  const signature = await executeTransaction(connection, wallet, transaction, {
    silent: false,
    signers: [entrantsKeyPair]
  });
  return { signature, blazeId };
};

/**
 * Update single blaze
 * @param connection
 * @param wallet
 * @param params
 * blazeId: PublicKey;
 * projectId: PublicKey;
 * tokenMint?: PublicKey;
 * tokenAccount?: PublicKey;
 * treasury?: PublicKey;
 * burnRate?: number;
 * entrantFee: BN;
 * start: BN;
 * end: BN;
 * maxEntrantsPerWalletRate?: number;
 * category?: string;
 * @returns signature of the transaction
 */
export const updateBlaze = async (
  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<string> => {
  const transaction = new Transaction();
  await withUpdateBlaze(transaction, connection, wallet, params);

  const signature = await executeTransaction(connection, wallet, transaction);
  return signature;
};

/**
 * Buy multiple entrants from a blaze
 * @param connection
 * @param wallet
 * @param params
 * quantity: number;
 * blazeId: PublicKey;
 * @returns
 */
export const redeemEntrants = async (
  connection: Connection,
  wallet: Wallet,
  params: {
    quantity: number;
    blazeId: PublicKey;
  }
): Promise<string> => {
  const transaction = new Transaction();

  const blaze = await getBlaze(connection, params.blazeId);

  const remainingAccountsForRedeem = await withRemainingAccountsForRedeem(
    transaction,
    connection,
    wallet,
    {
      tokenMint: blaze.parsed.tokenMint,
      treasury: blaze.parsed.treasury
    }
  );

  await withRedeemEntrants(transaction, connection, wallet, {
    quantity: params.quantity,
    blazeId: params.blazeId,
    entrants: blaze.parsed.entrants,
    remainingAccountsForRedeem
  });

  const signature = await executeTransaction(connection, wallet, transaction);
  return signature;
};

/**
 *
 * @param connection
 * @param wallet
 * @param params
 * blazeId: PublicKey;
 * @returns signature
 */
export const resolveBlaze = async (
  connection: Connection,
  wallet: Wallet,
  params: {
    blazeId: PublicKey;
  }
): Promise<string> => {
  const transaction = new Transaction();

  const blaze = await getBlaze(connection, params.blazeId);

  await withResolveBlaze(transaction, connection, wallet, {
    blazeId: params.blazeId,
    entrants: blaze.parsed.entrants
  });

  const signature = await executeTransaction(connection, wallet, transaction);
  return signature;
};

export const claimPrize = async (
  connection: Connection,
  wallet: Wallet,
  params: {
    blazeId: PublicKey;
  }
): Promise<string> => {
  const transaction = new Transaction();

  const blaze = await getBlaze(connection, params.blazeId);

  await withClaimPrize(transaction, connection, wallet, {
    blazeId: params.blazeId,
    prizeMint: blaze.parsed.prizeMint
  });

  const signature = await executeTransaction(connection, wallet, transaction);
  return signature;
};

export const closeBlaze = async (
  connection: Connection,
  wallet: Wallet,
  params: {
    blazeId: PublicKey;
  }
): Promise<string> => {
  const transaction = new Transaction();

  const blaze = await getBlaze(connection, params.blazeId);

  await withCloseBlaze(transaction, connection, wallet, {
    blazeId: params.blazeId,
    projectId: blaze.parsed.project,
    prizeMint: blaze.parsed.prizeMint,
    entrants: blaze.parsed.entrants
  });

  const signature = await executeTransaction(connection, wallet, transaction);
  return signature;
};
