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


import ProgramLoader from "./ProgramLoader";
import { AuctionManager as AuctionManagerType } from "../types/auction-manager";



/**
 * AuctionManager
 * 
 *  @description This is the main contract for the auction. It is responsible for managing the auction, including creating and updating the auction, and executing bids.
 * 
 *  - create a new auction manager
 *  - get an auction manager
 *  - fetch many auction managers
 *  - get all auction managers
 */

export default class AuctionManager {
    public constructor(
        protected readonly programLoader: ProgramLoader
    ) {
    }


    /**
     * Create a new auction manager
     * @param {PublicKey} nft - The NFT to create the auction manager for
     * @param {PublicKey} creator - The creator of the auction manager
     * @param {PublicKey} tokenProgramId - The program ID of the nft
     * @returns {TransactionInstruction} The transaction instruction
     */
    public async create(nft: PublicKey, creator: PublicKey, tokenProgramId: PublicKey): Promise<TransactionInstruction> {
        try {
            // 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);
            // console.log("[*] Application state: ", applicationState);
            // console.log("[*] Application state bump: ", applicationStateBump);

            const seeds2 = [anchor.utils.bytes.utf8.encode("auction_manager"), Uint8Array.from(nft.toBuffer()), Uint8Array.from(creator.toBuffer())];
            const [auctionManager, auctionManagerBump] = PublicKey.findProgramAddressSync(seeds2, this.programLoader.program.programId);
            console.log("[*] Auction manager: ", auctionManager);
            console.log("[*] Auction manager bump: ", auctionManagerBump);

            const inx = await this.programLoader.program.methods
                .createAuctionManager()
                .accounts({
                    applicationState: this.programLoader.applicationState,
                    nft,
                    systemProgram: SystemProgram.programId,
                    auctionManager,
                    tokenProgram: tokenProgramId,
                    rent: anchor.web3.SYSVAR_RENT_PUBKEY,
                    user: creator
                })
                .instruction();

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


    /**
     * Get an auction manager
     * @param {PublicKey} managerPubkey - The public key of the auction manager
     * @returns {AuctionManagerType} The auction manager account
     */
    public async get(managerPubkey: PublicKey): Promise<AuctionManagerType> {
        try {
            const auctionManagerAccount: AuctionManagerType = await this.programLoader.program.account.auctionManager.fetch(managerPubkey);
            console.log("[*] Auction manager account: ", JSON.stringify(auctionManagerAccount, null, 2));
            return auctionManagerAccount;
        } catch (e: any) {
            console.error(e);
            throw new Error(`Failed to get auction manager: ${e.message}`);
        }
    }


    /**
     * Get all auction managers
     * @returns {AuctionManagerType[]} The auction manager accounts
     */
    public async getAll(): Promise<AuctionManagerType[]> {
        let auctionManagerAccounts = await this.programLoader.program.account.auctionManager.all();
        console.log("[*] Auction manager accounts: ", JSON.stringify(auctionManagerAccounts, null, 2));
        return auctionManagerAccounts as unknown as AuctionManagerType[];
    }


    /**
     * Fetch many auction managers
     * @param {PublicKey[]} managerPubkeys - The public keys of the auction managers
     * @returns {AuctionManagerType[]} The auction manager accounts
     */
    public async fetchMany(managerPubkeys: PublicKey[]): Promise<AuctionManagerType[]> {
        let auctionManagerAccounts = await this.programLoader.program.account.auctionManager.fetchMultiple(managerPubkeys);
        console.log("[*] Auction manager accounts: ", JSON.stringify(auctionManagerAccounts, null, 2));
        return auctionManagerAccounts as unknown as AuctionManagerType[];
    }

}
