import { PublicKey, SystemProgram, Transaction, TransactionInstruction } from "@solana/web3.js";
import * as anchor from "@coral-xyz/anchor";

import ProgramLoader from "./ProgramLoader";
import { BidReceiptState, BidReceipt as BidReceiptType, ReceiptInfo } from "../types/bid-receipt";
import Listing from "./Listing";
import { ASSOCIATED_TOKEN_PROGRAM_ID, createAssociatedTokenAccountInstruction, getAssociatedTokenAddress, TOKEN_PROGRAM_ID } from "@solana/spl-token";
import { checkEnumState } from "../helper/check-enum-status.helper";
import config from "../config";




/**
 * BidReceipt
 * 
 * @description A class for managing bid receipts
 * 
 *  - create a bid receipt
 *  - get a bid receipt
 *  - fetch many bid receipts
 *  - get all bid receipts
 *  - get bid receipt by state
 *  - get user bid receipt on a listing
 *  - cancel a bid receipt
 *  - refund a bid
 *  - increase bid
 */
export default class BidReceipt {
    public constructor(protected readonly programLoader: ProgramLoader) {
    }

    private getListingProgram(): Listing {
        return new Listing(this.programLoader);
    }


    /**
     * @description Create a bid receipt
     * 
     * @param {PublicKey} listingPubkey - The public key of the listing
     * @param {PublicKey} bidder - The public key of the bidder
     * @param {number} price - The price of the bid
     * @returns {TransactionInstruction[]}
     */
    public async create(listingPubkey: PublicKey, bidder: PublicKey, price: number, tokenMintProgramId: PublicKey,): Promise<TransactionInstruction[]> {
        try {
            var listingProgram = this.getListingProgram();
            const listingData = await listingProgram.get(listingPubkey);

            // const seeds1 = [anchor.utils.bytes.utf8.encode("deserialize"), Uint8Array.from(this.programLoader.wallet.publicKey.toBuffer())]
            // const [applicationState, applicationStateBump] = PublicKey.findProgramAddressSync(seeds1, this.programLoader.program.programId);
            let appState = await this.programLoader.program.account.applicationState.fetch(this.programLoader.applicationState)

            const seeds2 = [anchor.utils.bytes.utf8.encode("auction_manager"), Uint8Array.from(listingData.nft.toBuffer()), Uint8Array.from(listingData.authority.toBuffer())];
            const [auctionManager, auctionManagerBump] = PublicKey.findProgramAddressSync(seeds2, this.programLoader.program.programId);

            const seeds3 = [anchor.utils.bytes.utf8.encode("listing"), Uint8Array.from(listingData.nft.toBuffer()), Uint8Array.from(listingData.authority.toBuffer()), Uint8Array.from(auctionManager.toBuffer()), Uint8Array.from(listingData.tokenMint.toBuffer())];
            const [listing, listingBump] = PublicKey.findProgramAddressSync(seeds3, this.programLoader.program.programId);
            console.log("[*] Listing: ", listing);
            console.log("[*] Listing bump: ", listingBump);

            const seeds4 = [anchor.utils.bytes.utf8.encode("bid_receipt"), Uint8Array.from(listing.toBuffer()), Uint8Array.from(bidder.toBuffer()), Uint8Array.from(listingData.tokenMint.toBuffer())];
            const [bidReceipt, bidReceiptBump] = PublicKey.findProgramAddressSync(seeds4, this.programLoader.program.programId);
            console.log("[*] Bid receipt: ", bidReceipt);
            console.log("[*] Bid receipt bump: ", bidReceiptBump);


            const seeds = [anchor.utils.bytes.utf8.encode("user_account"), Uint8Array.from(bidder.toBuffer()), Uint8Array.from(appState.listingAuthority.toBuffer()), anchor.utils.bytes.utf8.encode("deserialize")];
            const [userdata, userdataBump] = PublicKey.findProgramAddressSync(seeds, this.programLoader.program.programId);
            console.log("[*] Userdata: ", userdata);
            console.log("[*] Userdata bump: ", userdataBump);


            const nftEscrow = await getAssociatedTokenAddress(listingData.nft, auctionManager, true);
            const proceedsWallet = await getAssociatedTokenAddress(listingData.tokenMint, listingData.authority, true, tokenMintProgramId);


            const bidderTokenAccount = await getAssociatedTokenAddress(listingData.tokenMint, bidder, true, tokenMintProgramId);
            const bidderNftAccount = await getAssociatedTokenAddress(listingData.nft, bidder, true);
            const tokenEscrow = await getAssociatedTokenAddress(listingData.tokenMint, auctionManager, true, tokenMintProgramId);
            const feeTokenAccount = await getAssociatedTokenAddress(listingData.tokenMint, appState.feeAccount, true, tokenMintProgramId);
            console.log("[*] BIDDER TOKEN account: ", bidderTokenAccount);
            console.log("[*] BIDDER NFT account: ", bidderNftAccount);
            console.log("[*] AUCTION MANAGER TOKEN escrow: ", tokenEscrow);
            console.log("[*] FEE TOKEN account: ", feeTokenAccount);
            const txns: TransactionInstruction[] = [];
            const bidderTokenAccountInfo = await this.programLoader.connection.getAccountInfo(bidderTokenAccount);
            const bidderNftAccountInfo = await this.programLoader.connection.getAccountInfo(bidderNftAccount);
            const tokenEscrowInfo = await this.programLoader.connection.getAccountInfo(tokenEscrow);
            const feeTokenAccountInfo = await this.programLoader.connection.getAccountInfo(feeTokenAccount);


            if (!feeTokenAccountInfo) {
                const inx = createAssociatedTokenAccountInstruction(bidder, feeTokenAccount, appState.feeAccount, listingData.tokenMint, tokenMintProgramId);
                txns.push(inx);
            }
            if (!bidderTokenAccountInfo) {
                const inx = createAssociatedTokenAccountInstruction(bidder, bidderTokenAccount, bidder, listingData.tokenMint, tokenMintProgramId);
                txns.push(inx);
            }
            if (!bidderNftAccountInfo) {
                const inx = createAssociatedTokenAccountInstruction(bidder, bidderNftAccount, bidder, listingData.nft);
                txns.push(inx);
            }
            if (!tokenEscrowInfo) {
                const inx = createAssociatedTokenAccountInstruction(bidder, tokenEscrow, auctionManager, listingData.tokenMint, tokenMintProgramId);
                txns.push(inx);
            }


            const submittedAt = Math.floor(Date.now() / 1000);
            const inx = await this.programLoader.program.methods
                .bidOnListing(new anchor.BN(price), new anchor.BN(submittedAt))
                .accounts({
                    applicationState: this.programLoader.applicationState,
                    userdata,
                    bidReceipt,
                    user: bidder,
                    listing,
                    nft: listingData.nft,
                    auctionManager,
                    proceedsWallet,
                    nftEscrow,
                    bidderNftAccount,
                    bidderAccount: bidderTokenAccount,
                    tokenEscrow,
                    tokenMint: listingData.tokenMint,
                    tokenProgram: TOKEN_PROGRAM_ID,
                    associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
                    systemProgram: SystemProgram.programId,
                    feeAccount: appState.feeAccount,
                    feeTokenAccount,
                })
                .instruction()
            txns.push(inx);

            return txns;
        } catch (e: any) {
            console.error(e);
            throw new Error(`Failed to create bid receipt: ${e.message}`);
        }
    }


    /**
     * @description Create a transaction for creating a bid receipt
     * 
     * @param {PublicKey} listingPubkey - The public key of the listing
     * @param {PublicKey} bidder - The public key of the bidder
     * @param {number} price - The price of the bid
     * @returns {Transaction}
     */
    public async createTransaction(listingPubkey: PublicKey, bidder: PublicKey, price: number, tokenMintProgramId: PublicKey): Promise<Transaction> {
        try {
            let txnIns = await this.create(listingPubkey, bidder, price, tokenMintProgramId);
            let txn: Transaction = new Transaction();
            for (let tx of txnIns) {
                txn.add(tx);
            }
            txn.recentBlockhash = (await this.programLoader.getRecentBlockHash()).blockhash;
            txn.feePayer = bidder;

            return txn;
        } catch (e: any) {
            console.error(e);
            throw new Error(`Failed to create bid receipt: ${e.message}`);
        }
    }


    /**
     * @description Get a bid receipt
     * 
     * @param {PublicKey} bidReceiptPubkey - The public key of the bid receipt
     * @returns {BidReceiptType}
     */
    public async get(bidReceiptPubkey: PublicKey): Promise<BidReceiptType> {
        try {
            const bidReceipt = await this.programLoader.program.account.bidReceipt.fetch(bidReceiptPubkey);
            return bidReceipt as unknown as BidReceiptType;
        } catch (e: any) {
            console.error(e);
            throw new Error(`Failed to get bid receipt: ${e.message}`);
        }
    }


    /**
     * @description Fetch many bid receipts
     * 
     * @param {PublicKey[]} bidReceiptPubkeys - The public keys of the bid receipts
     * @returns {BidReceiptType[]}
     */
    public async fetchMany(bidReceiptPubkeys: PublicKey[]): Promise<BidReceiptType[]> {
        try {
            const bidReceipts = await this.programLoader.program.account.bidReceipt.fetchMultiple(bidReceiptPubkeys);
            console.log("[*] Bid receipts: ", JSON.stringify(bidReceipts, null, 4));
            return bidReceipts as unknown as BidReceiptType[];
        } catch (e: any) {
            console.error(e);
            throw new Error(`Failed to fetch many bid receipts: ${e.message}`);
        }
    }


    /**
     * @description Get all bid receipts
     * 
     * @returns {BidReceiptType[]}
     */
    public async getAll(): Promise<BidReceiptType[]> {
        try {
            const bidReceipts = await this.programLoader.program.account.bidReceipt.all();
            return bidReceipts as unknown as BidReceiptType[];
        } catch (e: any) {
            console.error(e);
            throw new Error(`Failed to get all bid receipts: ${e.message}`);
        }
    }


    /**
     * @description Get bid receipts by state
     * 
     * @param {BidReceiptState} state - The state of the bid receipts
     * @returns {BidReceiptType[]}
     */
    public async getByState(state: BidReceiptState): Promise<BidReceiptType[]> {
        try {
            const bidReceipts = await this.getAll();
            return bidReceipts.filter((bidReceipt) => bidReceipt.state === state);
        } catch (e: any) {
            console.error(e);
            throw new Error(`Failed to get bid receipts by state: ${e.message}`);
        }
    }


    /**
     * @description Get a user's bid on a listing
     * 
     * @param {PublicKey} listingPubkey - The public key of the listing
     * @param {PublicKey} user - The public key of the user
     * @returns {BidReceiptType|null}
     */
    public async getUserBidOnListing(listingPubkey: PublicKey, user: PublicKey): Promise<BidReceiptType | null> {
        try {
            var listingProgram = this.getListingProgram();
            let listing = await listingProgram.get(listingPubkey);
            let bids = await this.fetchMany(listing.bidReceipts);
            let userBid = bids.find(bid => bid.bidder.equals(user));

            return userBid;
        } catch (e: any) {
            console.error(e);
            throw new Error(`Failed to get user bid on listing: ${e.message}`);
        }
    }


    /**
     * @description Cancel a bid receipt
     * 
     * @param {PublicKey} bidReceiptPubkey - The public key of the bid receipt
     * @param {PublicKey} bidder - The public key of the bidder
     * @returns {Transaction}
     */
    public async cancel(bidReceiptPubkey: PublicKey, bidder: PublicKey, tokenMintProgramId: PublicKey): Promise<Transaction> {
        try {
            const bidReceiptData = await this.get(bidReceiptPubkey);
            var listingProgram = this.getListingProgram();
            const listingData = await listingProgram.get(bidReceiptData.listing);
            if (!bidReceiptData.bidder.equals(bidder)) {
                throw new Error("You cannot cancel another user's bid");
            }

            // const seeds1 = [anchor.utils.bytes.utf8.encode("deserialize"), Uint8Array.from(this.programLoader.wallet.publicKey.toBuffer())]
            // const [applicationState, applicationStateBump] = PublicKey.findProgramAddressSync(seeds1, this.programLoader.program.programId);
            let appState = await this.programLoader.program.account.applicationState.fetch(this.programLoader.applicationState)

            const seeds2 = [anchor.utils.bytes.utf8.encode("auction_manager"), Uint8Array.from(listingData.nft.toBuffer()), Uint8Array.from(listingData.authority.toBuffer())];
            const [auctionManager, auctionManagerBump] = PublicKey.findProgramAddressSync(seeds2, this.programLoader.program.programId);

            const seeds3 = [anchor.utils.bytes.utf8.encode("listing"), Uint8Array.from(listingData.nft.toBuffer()), Uint8Array.from(listingData.authority.toBuffer()), Uint8Array.from(auctionManager.toBuffer()), Uint8Array.from(listingData.tokenMint.toBuffer())];
            const [listing, listingBump] = PublicKey.findProgramAddressSync(seeds3, this.programLoader.program.programId);
            console.log("[*] Listing: ", listing);
            console.log("[*] Listing bump: ", listingBump);

            const seeds4 = [anchor.utils.bytes.utf8.encode("bid_receipt"), Uint8Array.from(listing.toBuffer()), Uint8Array.from(bidder.toBuffer()), Uint8Array.from(listingData.tokenMint.toBuffer())];
            const [bidReceipt, bidReceiptBump] = PublicKey.findProgramAddressSync(seeds4, this.programLoader.program.programId);
            console.log("[*] Bid receipt: ", bidReceipt);
            console.log("[*] Bid receipt bump: ", bidReceiptBump);



            const seeds = [anchor.utils.bytes.utf8.encode("user_account"), Uint8Array.from(bidder.toBuffer()), Uint8Array.from(appState.listingAuthority.toBuffer()), anchor.utils.bytes.utf8.encode("deserialize")];
            const [userdata, userdataBump] = PublicKey.findProgramAddressSync(seeds, this.programLoader.program.programId);
            console.log("[*] Userdata: ", userdata);
            console.log("[*] Userdata bump: ", userdataBump);

            const nftEscrow = await getAssociatedTokenAddress(listingData.nft, auctionManager, true);
            const nftOwner = await getAssociatedTokenAddress(listingData.nft, listingData.authority, true);
            console.log("[*] AUCTION MANAGER NFT escrow: ", nftEscrow);
            console.log("[*] LISTER NFT owner: ", nftOwner);

            const bidderTokenAccount = await getAssociatedTokenAddress(listingData.tokenMint, bidder, true, tokenMintProgramId);
            const bidderNftAccount = await getAssociatedTokenAddress(listingData.nft, bidder, true);
            const tokenEscrow = await getAssociatedTokenAddress(listingData.tokenMint, auctionManager, true, tokenMintProgramId);

            console.log("[*] BIDDER TOKEN account: ", bidderTokenAccount);
            console.log("[*] BIDDER NFT account: ", bidderNftAccount);
            console.log("[*] AUCTION MANAGER TOKEN escrow: ", tokenEscrow);

            let data: ReceiptInfo[] = await Promise.all(listingData.bidReceipts.map(async key => {
                let bidReceipt = await this.get(key);
                return {
                    amount: bidReceipt.amount,
                    key
                };
            }))


            const submittedAt = Math.floor(Date.now() / 1000);
            const tx = await this.programLoader.program.methods
                .cancelBid(data, new anchor.BN(submittedAt))
                .accounts({
                    applicationState: this.programLoader.applicationState,
                    userdata,
                    bidReceipt,
                    user: bidder,
                    listing,
                    nft: listingData.nft,
                    auctionManager,
                    cancellerTokenAccount: bidderTokenAccount,
                    tokenEscrow,
                    tokenMint: listingData.tokenMint,
                    tokenProgram: TOKEN_PROGRAM_ID,
                    associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
                    systemProgram: SystemProgram.programId,
                    feeAccount: appState.feeAccount
                })
                .transaction();
            tx.recentBlockhash = (await this.programLoader.getRecentBlockHash()).blockhash;
            tx.feePayer = bidder;

            return tx;
        } catch (e: any) {
            console.error(e);
            throw new Error(`Failed to cancel bid receipt: ${e.message}`);
        }
    }


    /**
     * @description Refund a bid receipt
     * 
     * @param {PublicKey} bidReceiptPubkey - The public key of the bid receipt
     * @param {PublicKey} bidder - The public key of the bidder
     * @returns {TransactionInstruction}
     */
    public async refund(bidReceiptPubkey: PublicKey, bidder: PublicKey, tokenMintProgramId: PublicKey): Promise<TransactionInstruction> {
        try {
            const bidReceiptData = await this.get(bidReceiptPubkey);
            console.log("[*] Bid receipt data: ", JSON.stringify(bidReceiptData, null, 4));
            var listingProgram = this.getListingProgram();
            const listingData = await listingProgram.get(bidReceiptData.listing);
            console.log("[*] Listing data: ", JSON.stringify(listingData, null, 4));
            if (!bidReceiptData.bidder.equals(bidder)) {
                throw new Error("You cannot cancel another user's bid");
            }
            if (!checkEnumState(bidReceiptData.state as any, BidReceiptState.Active)) {
                throw new Error("You cannot refund a bid that is not active");
            }

            // const seeds1 = [anchor.utils.bytes.utf8.encode("deserialize"), Uint8Array.from(this.programLoader.wallet.publicKey.toBuffer())]
            // const [applicationState, applicationStateBump] = PublicKey.findProgramAddressSync(seeds1, this.programLoader.program.programId);
            let appState = await this.programLoader.program.account.applicationState.fetch(this.programLoader.applicationState);
            console.log("[*] Application state: ", JSON.stringify(appState, null, 4));

            const seeds2 = [anchor.utils.bytes.utf8.encode("auction_manager"), Uint8Array.from(listingData.nft.toBuffer()), Uint8Array.from(listingData.authority.toBuffer())];
            const [auctionManager, auctionManagerBump] = PublicKey.findProgramAddressSync(seeds2, this.programLoader.program.programId);
            console.log("[*] AUCTION MANAGER", auctionManager);

            const seeds3 = [anchor.utils.bytes.utf8.encode("listing"), Uint8Array.from(listingData.nft.toBuffer()), Uint8Array.from(listingData.authority.toBuffer()), Uint8Array.from(auctionManager.toBuffer()), Uint8Array.from(listingData.tokenMint.toBuffer())];
            const [listing, listingBump] = PublicKey.findProgramAddressSync(seeds3, this.programLoader.program.programId);
            console.log("[*] Listing: ", listing);
            console.log("[*] Listing bump: ", listingBump);

            const seeds4 = [anchor.utils.bytes.utf8.encode("bid_receipt"), Uint8Array.from(listing.toBuffer()), Uint8Array.from(bidder.toBuffer()), Uint8Array.from(listingData.tokenMint.toBuffer())];
            const [bidReceipt, bidReceiptBump] = PublicKey.findProgramAddressSync(seeds4, this.programLoader.program.programId);
            console.log("[*] Bid receipt: ", bidReceipt);
            console.log("[*] Bid receipt bump: ", bidReceiptBump);



            const seeds = [anchor.utils.bytes.utf8.encode("user_account"), Uint8Array.from(bidder.toBuffer()), Uint8Array.from(appState.listingAuthority.toBuffer()), anchor.utils.bytes.utf8.encode("deserialize")];
            const [userdata, userdataBump] = PublicKey.findProgramAddressSync(seeds, this.programLoader.program.programId);
            console.log("[*] Userdata: ", userdata);
            console.log("[*] Userdata bump: ", userdataBump);

            const nftEscrow = await getAssociatedTokenAddress(listingData.nft, auctionManager, true);
            const nftOwner = await getAssociatedTokenAddress(listingData.nft, listingData.authority, true);
            console.log("[*] AUCTION MANAGER NFT escrow: ", nftEscrow);
            console.log("[*] LISTER NFT owner: ", nftOwner);

            const bidderTokenAccount = await getAssociatedTokenAddress(listingData.tokenMint, bidder, true, tokenMintProgramId);
            const bidderNftAccount = await getAssociatedTokenAddress(listingData.nft, bidder, true);
            const tokenEscrow = await getAssociatedTokenAddress(listingData.tokenMint, auctionManager, true, tokenMintProgramId);

            console.log("[*] BIDDER TOKEN account: ", bidderTokenAccount);
            console.log("[*] BIDDER NFT account: ", bidderNftAccount);
            console.log("[*] AUCTION MANAGER TOKEN escrow: ", tokenEscrow);


            const refundedAt = Math.floor(Date.now() / 1000);

            let inx = await this.programLoader.program.methods
                .refundBid(new anchor.BN(refundedAt))
                .accounts({
                    applicationState: this.programLoader.applicationState,
                    user: bidder,
                    auctionManager,
                    listing,
                    nft: listingData.nft,
                    userdata,
                    userRefundAccount: bidderTokenAccount,
                    refundBidReceipt: bidReceipt,
                    tokenEscrow,
                    tokenMint: listingData.tokenMint,
                    tokenProgram: TOKEN_PROGRAM_ID,
                    associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
                    systemProgram: SystemProgram.programId
                })
                .instruction()

            return inx;
        } catch (e: any) {
            console.error(e);
            throw new Error(`Failed to refund bid receipt: ${e.message}`);
        }
    }


    /**
     * @description Increase a bid
     * 
     * @param {PublicKey} bidReceiptPubkey - The public key of the bid receipt
     * @param {PublicKey} bidder - The public key of the bidder
     * @param {number} newAmount - The new bid amount
     * @returns {Transaction}
     */
    public async increase(bidReceiptPubkey: PublicKey, bidder: PublicKey, newAmount: number, tokenMintProgramId: PublicKey): Promise<Transaction> {
        try {
            const bidReceiptData = await this.get(bidReceiptPubkey);
            var listingProgram = this.getListingProgram();
            const listingData = await listingProgram.get(bidReceiptData.listing);
            if (!bidReceiptData.bidder.equals(bidder)) {
                throw new Error("You cannot increase another user's bid");
            }
            if (bidReceiptData.state !== BidReceiptState.Active) {
                throw new Error("You cannot increase a bid that is not active");
            }


            // const seeds1 = [anchor.utils.bytes.utf8.encode("deserialize"), Uint8Array.from(this.programLoader.wallet.publicKey.toBuffer())]
            // const [applicationState, applicationStateBump] = PublicKey.findProgramAddressSync(seeds1, this.programLoader.program.programId);
            let appState = await this.programLoader.program.account.applicationState.fetch(this.programLoader.applicationState)

            const seeds2 = [anchor.utils.bytes.utf8.encode("auction_manager"), Uint8Array.from(listingData.nft.toBuffer()), Uint8Array.from(listingData.authority.toBuffer())];
            const [auctionManager, auctionManagerBump] = PublicKey.findProgramAddressSync(seeds2, this.programLoader.program.programId);

            const seeds3 = [anchor.utils.bytes.utf8.encode("listing"), Uint8Array.from(listingData.nft.toBuffer()), Uint8Array.from(listingData.authority.toBuffer()), Uint8Array.from(auctionManager.toBuffer()), Uint8Array.from(listingData.tokenMint.toBuffer())];
            const [listing, listingBump] = PublicKey.findProgramAddressSync(seeds3, this.programLoader.program.programId);
            console.log("[*] Listing: ", listing);
            console.log("[*] Listing bump: ", listingBump);

            const seeds4 = [anchor.utils.bytes.utf8.encode("bid_receipt"), Uint8Array.from(listing.toBuffer()), Uint8Array.from(bidder.toBuffer()), Uint8Array.from(listingData.tokenMint.toBuffer())];
            const [bidReceipt, bidReceiptBump] = PublicKey.findProgramAddressSync(seeds4, this.programLoader.program.programId);
            console.log("[*] Bid receipt: ", bidReceipt);
            console.log("[*] Bid receipt bump: ", bidReceiptBump);



            const seeds = [anchor.utils.bytes.utf8.encode("user_account"), Uint8Array.from(bidder.toBuffer()), Uint8Array.from(appState.listingAuthority.toBuffer()), anchor.utils.bytes.utf8.encode("deserialize")];
            const [userdata, userdataBump] = PublicKey.findProgramAddressSync(seeds, this.programLoader.program.programId);
            console.log("[*] Userdata: ", userdata);
            console.log("[*] Userdata bump: ", userdataBump);

            const nftEscrow = await getAssociatedTokenAddress(listingData.nft, auctionManager, true);
            const nftOwner = await getAssociatedTokenAddress(listingData.nft, listingData.authority, true);
            console.log("[*] AUCTION MANAGER NFT escrow: ", nftEscrow);
            console.log("[*] LISTER NFT owner: ", nftOwner);

            const bidderTokenAccount = await getAssociatedTokenAddress(listingData.tokenMint, bidder, true, tokenMintProgramId);
            const bidderNftAccount = await getAssociatedTokenAddress(listingData.nft, bidder, true);
            const tokenEscrow = await getAssociatedTokenAddress(listingData.tokenMint, auctionManager, true, tokenMintProgramId);

            console.log("[*] BIDDER TOKEN account: ", bidderTokenAccount);
            console.log("[*] BIDDER NFT account: ", bidderNftAccount);
            console.log("[*] AUCTION MANAGER TOKEN escrow: ", tokenEscrow);


            const updatedAt = Math.floor(Date.now() / 1000);
            const tx = await this.programLoader.program.methods
                .increaseBid(new anchor.BN(newAmount), new anchor.BN(updatedAt))
                .accounts({
                    applicationState: this.programLoader.applicationState,
                    userdata,
                    bidReceipt,
                    user: bidder,
                    listing,
                    nft: listingData.nft,
                    auctionManager,
                    bidderAccount: bidderTokenAccount,
                    tokenEscrow: tokenEscrow,
                    tokenMint: listingData.tokenMint,
                    tokenProgram: TOKEN_PROGRAM_ID,
                    associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
                    systemProgram: SystemProgram.programId,
                    feeAccount: appState.feeAccount
                })
                .transaction();
            tx.recentBlockhash = (await this.programLoader.getRecentBlockHash()).blockhash;
            tx.feePayer = bidder;

            return tx;
        } catch (e: any) {
            console.error(e);
            throw new Error(`Failed to increase bid receipt: ${e.message}`);
        }
    }
}