import * as anchor from '@project-serum/anchor';
import * as base58 from 'bs58';
import {Commitment, Connection, Keypair, PublicKey, SystemProgram, SYSVAR_RENT_PUBKEY} from '@solana/web3.js';
import NodeWallet from '@project-serum/anchor/dist/cjs/nodewallet';
import {ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID} from "@solana/spl-token";
import {
    ASSET_EVENT_NAME,
    AssetEvent,
    AssetMintingLib,
    COLLECTION_EVENT_NAME,
    CollectionEvent,
    MINT_EVENT_NAME,
    MintEvent,
    SUB_COLLECTION_EVENT_NAME,
    SubCollectionEvent
} from '@mirrorworld/library.assetminting';
import {PROGRAM_ID as mpl_programId} from "@metaplex-foundation/mpl-token-metadata";
import {nanoid} from "nanoid";


const userWalletSecretKey = '9LYsKjFgx2QEV41tjaoQdcMXbTWKLLkWszzMJYyWZQxPwRHxp7TcG9zShrT3hDmsKZSYesU4UGUFq2DG76sQqJA';
const userWalletKeypair = Keypair.fromSecretKey(base58.decode(userWalletSecretKey));

const signingAuthorityWalletSecretKey = '3ZcBG7cAJ8mHTAWuiENvWqTGPNnW9nN17JPw2jSpfx5RNrM1zQ1gN1oVwoNX84HxKCU31BQRAq8rLMDSGTtK27ut';
const signingAuthorityWalletKeypair = Keypair.fromSecretKey(base58.decode(signingAuthorityWalletSecretKey));

const uselessKeypair = Keypair.generate();

const creators = [
    {
        address: signingAuthorityWalletKeypair.publicKey,
        verified: true,
        share: 50
    },
    {
        address: userWalletKeypair.publicKey,
        verified: false,
        share: 45
    },
    {
        address: uselessKeypair.publicKey,
        verified: false,
        share: 5
    }

];

const programId = new PublicKey('qpPNPbKoWFUwSRDfqUCBE9uyQZB4k2tH8zRBpTKkfSf');

const commitment: Commitment = 'processed';
const connection = new Connection('https://solana-devnet.g.alchemy.com/v2/Yilcd9Z0CnrxgtKoKuzp0xplxjiXgxv0', {
    commitment,
    wsEndpoint: 'wss://solana-devnet.g.alchemy.com/v2/Yilcd9Z0CnrxgtKoKuzp0xplxjiXgxv0'
});

// const connection = new Connection('https://api.devnet.solana.com', {
//     commitment,
//     wsEndpoint: 'wss://api.devnet.solana.com/'
// });

const options = anchor.AnchorProvider.defaultOptions();
const userWallet = new NodeWallet(signingAuthorityWalletKeypair);
const provider = new anchor.AnchorProvider(connection, userWallet, options);
anchor.setProvider(provider);

const assetMintingLib = new AssetMintingLib(programId, connection, userWallet);

const NAME = "MirrorWorld";

const tokenName = "Collection#0";
const tokenSymbol = "Collection";
const tokenUrl = "https://m6zsbnhyclkmaqkw62dyxpxo3qxfgb3niok3dv3sczi7epzazrja.arweave.net/Z7MgtPgS1MBBVvaHi77u3C5TB21DlbHXchZR8j8gzFI";

let collectionUuid: string = nanoid();
console.log("Collection Uuid: ", collectionUuid);
let subCollectionUuid: string = nanoid();
console.log("Sub Collection Uuid: ", subCollectionUuid);
let assetUuid: string = nanoid();
console.log("Asset Uuid: ", assetUuid);

const timeDelay = 2500;

// Event Handlers
const handleMintEvent = (ev: MintEvent) =>
    console.log(`${MINT_EVENT_NAME} ==>`, {
        mint_uuid: ev.mintUuid,
        mint_address: ev.mintAddress.toBase58()
    });

const mintEventId = assetMintingLib.addMintEventListener(handleMintEvent);

const handleCollectionEvent = (ev: CollectionEvent) =>
    console.log(`${COLLECTION_EVENT_NAME} ==>`, {
        mint_uuid: ev.mintUuid,
        mint_address: ev.mintAddress.toBase58(),
        metadata_address: ev.metadataAddress.toBase58(),
        master_edition_address: ev.masterEditionAddress.toBase58(),
        owner: ev.owner.toBase58()
    });

const collectionEventId = assetMintingLib.addCollectionEventListener(handleCollectionEvent);

const handleSubCollectionEvent = (ev: SubCollectionEvent) =>
    console.log(`${SUB_COLLECTION_EVENT_NAME} ==>`, {
        mint_uuid: ev.mintUuid,
        mint_address: ev.mintAddress.toBase58(),
        metadata_address: ev.metadataAddress.toBase58(),
        master_edition_address: ev.masterEditionAddress.toBase58(),
        owner: ev.owner.toBase58(),
        collection_uuid: ev.collectionUuid,
        collection_mint_address: ev.collectionMintAddress.toBase58(),
        collection_metadata_address: ev.collectionMetadataAddress.toBase58(),
        collection_master_edition_address: ev.collectionMasterEditionAddress.toBase58(),
    });

const subCollectionEventId = assetMintingLib.addSubCollectionEventListener(handleSubCollectionEvent);

const handleAssetEvent = (ev: AssetEvent) =>
    console.log(`${ASSET_EVENT_NAME} ==>`, {
        mint_uuid: ev.mintUuid,
        mint_address: ev.mintAddress.toBase58(),
        metadata_address: ev.metadataAddress.toBase58(),
        master_edition_address: ev.masterEditionAddress.toBase58(),
        owner: ev.owner.toBase58(),
        collection_uuid: ev.collectionUuid,
        collection_mint_address: ev.collectionMintAddress.toBase58(),
        collection_metadata_address: ev.collectionMetadataAddress.toBase58(),
        collection_master_edition_address: ev.collectionMasterEditionAddress.toBase58(),
    });

const assetEventId = assetMintingLib.addAssetEventListener(handleAssetEvent);

(async () => {

    await initializeConfig();

    await createCollectionToken();

    await createSubCollectionToken();

    await createAssetToken();

    await removeEventListener();

})();

async function initializeConfig() {
    console.log("Started initializeConfig");

    const [configPda] = await assetMintingLib.getConfigAccountPdaAndBump(NAME);

    if (!(await assetMintingLib.isPdaAddressInitialize(configPda))) {
        let tx = await assetMintingLib.createInitializeConfigTransaction(signingAuthorityWalletKeypair.publicKey, signingAuthorityWalletKeypair.publicKey, NAME);

        await assetMintingLib.addFeePayerAndRecentBlockHashInTransaction(tx, signingAuthorityWalletKeypair.publicKey);

        assetMintingLib.signTransaction(tx, signingAuthorityWalletSecretKey);

        let txHash = await connection.sendRawTransaction(tx.serialize());
        console.log("Tx Hash: ", txHash);

        await delay(timeDelay);
    }
}

async function createCollectionToken() {
    console.log("Started createCollectionToken");

    const [collectionMintAccount] = await assetMintingLib.getMintAccountPdaAndBump(collectionUuid);
    console.log("Collection Mint Account: ", collectionMintAccount.toBase58());

    let tx = await assetMintingLib.createCollectionTokenTransaction(collectionUuid, userWalletKeypair.publicKey, signingAuthorityWalletKeypair.publicKey,
        userWalletKeypair.publicKey, userWalletKeypair.publicKey, tokenName, tokenSymbol, tokenUrl, true, 0, creators, NAME,
        SystemProgram.programId, SYSVAR_RENT_PUBKEY, ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, mpl_programId);

    // Adding Instructions for verifying the creators
    await assetMintingLib.createAndAddCreatorSignMetadataInstruction(tx, collectionUuid, userWalletKeypair.publicKey, mpl_programId);
    await assetMintingLib.createAndAddCreatorSignMetadataInstruction(tx, collectionUuid, uselessKeypair.publicKey, mpl_programId);

    await assetMintingLib.addFeePayerAndRecentBlockHashInTransaction(tx, userWalletKeypair.publicKey);

    assetMintingLib.signTransaction(tx, signingAuthorityWalletSecretKey);
    assetMintingLib.signTransaction(tx, userWalletSecretKey);
    assetMintingLib.signTransaction(tx, base58.encode(uselessKeypair.secretKey));

    let txHash = await connection.sendRawTransaction(tx.serialize(), {skipPreflight: false});

    console.log("Tx Hash: ", txHash);

    await delay(timeDelay);
}


async function createSubCollectionToken() {
    await delay(4000);
    console.log("Started createSubCollectionToken");

    const [subCollectionMintAccount] = await assetMintingLib.getMintAccountPdaAndBump(subCollectionUuid);
    console.log("Sub Collection Mint Account: ", subCollectionMintAccount.toBase58());

    let tx = await assetMintingLib.createSubCollectionTokenTransaction(collectionUuid, subCollectionUuid, userWalletKeypair.publicKey, signingAuthorityWalletKeypair.publicKey,
        userWalletKeypair.publicKey, userWalletKeypair.publicKey, tokenName + "1", tokenSymbol, tokenUrl, true, 0, creators, NAME,
        signingAuthorityWalletKeypair.publicKey, SystemProgram.programId, SYSVAR_RENT_PUBKEY, ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, mpl_programId);

    // Adding Instructions for verifying the creators
    await assetMintingLib.createAndAddCreatorSignMetadataInstruction(tx, subCollectionUuid, userWalletKeypair.publicKey, mpl_programId);
    await assetMintingLib.createAndAddCreatorSignMetadataInstruction(tx, subCollectionUuid, uselessKeypair.publicKey, mpl_programId);

    await assetMintingLib.addFeePayerAndRecentBlockHashInTransaction(tx, userWalletKeypair.publicKey);

    assetMintingLib.signTransaction(tx, signingAuthorityWalletSecretKey);
    assetMintingLib.signTransaction(tx, userWalletSecretKey);
    assetMintingLib.signTransaction(tx, base58.encode(uselessKeypair.secretKey));

    let txHash = await connection.sendRawTransaction(tx.serialize(), {skipPreflight: false});

    console.log("Tx Hash: ", txHash);

    await delay(timeDelay);
}

async function createAssetToken() {
    await delay(4000);
    console.log("Started createAssetToken");

    const [assetMintAccount] = await assetMintingLib.getMintAccountPdaAndBump(assetUuid);
    console.log("Asset Mint Account: ", assetMintAccount.toBase58());

    let tx = await assetMintingLib.createAssetTokenTransaction(assetUuid, subCollectionUuid, userWalletKeypair.publicKey, signingAuthorityWalletKeypair.publicKey,
        userWalletKeypair.publicKey, userWalletKeypair.publicKey, tokenName + "2", tokenSymbol, tokenUrl, true, 550, creators, NAME,
        signingAuthorityWalletKeypair.publicKey, SystemProgram.programId, SYSVAR_RENT_PUBKEY, ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, mpl_programId);

    await assetMintingLib.addFeePayerAndRecentBlockHashInTransaction(tx, userWalletKeypair.publicKey);

    // Adding Instructions for verifying the creators
    await assetMintingLib.createAndAddCreatorSignMetadataInstruction(tx, assetUuid, userWalletKeypair.publicKey, mpl_programId);
    await assetMintingLib.createAndAddCreatorSignMetadataInstruction(tx, assetUuid, uselessKeypair.publicKey, mpl_programId);

    assetMintingLib.signTransaction(tx, signingAuthorityWalletSecretKey);
    assetMintingLib.signTransaction(tx, userWalletSecretKey);
    assetMintingLib.signTransaction(tx, base58.encode(uselessKeypair.secretKey));

    let txHash = await connection.sendRawTransaction(tx.serialize(), {skipPreflight: false});

    console.log("Tx Hash: ", txHash);

    await delay(timeDelay);
}

async function removeEventListener() {
    console.log("Started removeEventListener");

    await delay(10 * timeDelay);

    await assetMintingLib.removeEventListener(mintEventId);
    await assetMintingLib.removeEventListener(collectionEventId);
    await assetMintingLib.removeEventListener(subCollectionEventId);
    await assetMintingLib.removeEventListener(assetEventId);
}

function delay(ms: number) {
    return new Promise(resolve => setTimeout(resolve, ms));
}
