import { getSchnorrAccount, getSchnorrWalletWithSecretKey } from '@aztec/accounts/schnorr';
import { generateSchnorrAccounts, getDeployedTestAccountsWallets } from '@aztec/accounts/testing';
import { FeeJuicePaymentMethodWithClaim, Fr, L1FeeJuicePortalManager, SponsoredFeePaymentMethod, createAztecNodeClient, createCompatibleClient, retryUntil } from '@aztec/aztec.js';
import { createEthereumChain, createExtendedL1Client } from '@aztec/ethereum';
import { TokenContract } from '@aztec/noir-contracts.js/Token';
import { createPXEService } from '@aztec/pxe/server';
import { getACVMConfig } from '../fixtures/get_acvm_config.js';
import { getBBConfig } from '../fixtures/get_bb_config.js';
import { getSponsoredFPCAddress, registerSponsoredFPC } from '../fixtures/utils.js';
const TOKEN_NAME = 'USDC';
const TOKEN_SYMBOL = 'USD';
const TOKEN_DECIMALS = 18n;
export async function setupTestWalletsWithTokens(pxeUrl, mintAmount, logger) {
    const WALLET_COUNT = 1; // TODO fix this to allow for 16 wallets again
    const pxe = await createCompatibleClient(pxeUrl, logger);
    const [recipientWallet, ...wallets] = (await getDeployedTestAccountsWallets(pxe)).slice(0, WALLET_COUNT + 1);
    const tokenAdmin = wallets[0];
    const tokenAddress = await deployTokenAndMint(wallets, tokenAdmin.getAddress(), mintAmount, undefined, logger);
    const tokenAdminWallet = await TokenContract.at(tokenAddress, tokenAdmin);
    return {
        pxe,
        wallets,
        tokenAdminWallet,
        tokenAdminAddress: tokenAdmin.getAddress(),
        tokenName: TOKEN_NAME,
        tokenAddress,
        recipientAddress: recipientWallet.getAddress(),
        recipientWallet
    };
}
export async function deploySponsoredTestWallets(pxe, mintAmount, logger, numberOfFundedWallets = 1) {
    const [recipient, ...funded] = await generateSchnorrAccounts(numberOfFundedWallets + 1);
    const recipientWallet = await getSchnorrWalletWithSecretKey(pxe, recipient.secret, recipient.signingKey, recipient.salt);
    const fundedAccounts = await Promise.all(funded.map((a)=>getSchnorrAccount(pxe, a.secret, a.signingKey, a.salt)));
    await registerSponsoredFPC(pxe);
    const wallets = await Promise.all(fundedAccounts.map(async (a)=>{
        const wallet = await a.getWallet();
        const paymentMethod = new SponsoredFeePaymentMethod(await getSponsoredFPCAddress());
        await a.deploy({
            fee: {
                paymentMethod
            }
        }).wait({
            timeout: 2400
        }); // increase timeout on purpose in order to account for two empty epochs
        logger.info(`Account deployed at ${a.getAddress()}`);
        return wallet;
    }));
    const tokenAdmin = wallets[0];
    const tokenAddress = await deployTokenAndMint(wallets, tokenAdmin.getAddress(), mintAmount, new SponsoredFeePaymentMethod(await getSponsoredFPCAddress()), logger);
    const tokenAdminWallet = await TokenContract.at(tokenAddress, tokenAdmin);
    return {
        pxe,
        wallets,
        tokenAdminAddress: tokenAdmin.getAddress(),
        tokenAdminWallet,
        tokenName: TOKEN_NAME,
        tokenAddress,
        recipientWallet,
        recipientAddress: recipientWallet.getAddress()
    };
}
export async function deployTestWalletWithTokens(pxeUrl, nodeUrl, l1RpcUrls, mnemonicOrPrivateKey, mintAmount, logger, numberOfFundedWallets = 1) {
    const pxe = await createCompatibleClient(pxeUrl, logger);
    const node = createAztecNodeClient(nodeUrl);
    const [recipient, ...funded] = await generateSchnorrAccounts(numberOfFundedWallets + 1);
    const recipientWallet = await getSchnorrWalletWithSecretKey(pxe, recipient.secret, recipient.signingKey, recipient.salt);
    const fundedAccounts = await Promise.all(funded.map((a)=>getSchnorrAccount(pxe, a.secret, a.signingKey, a.salt)));
    const claims = await Promise.all(fundedAccounts.map((a)=>bridgeL1FeeJuice(l1RpcUrls, mnemonicOrPrivateKey, pxe, a.getAddress(), undefined, logger)));
    // Progress by 3 L2 blocks so that the l1ToL2Message added above will be available to use on L2.
    await advanceL2Block(node);
    await advanceL2Block(node);
    await advanceL2Block(node);
    const wallets = await Promise.all(fundedAccounts.map(async (a, i)=>{
        const wallet = await a.getWallet();
        const paymentMethod = new FeeJuicePaymentMethodWithClaim(wallet, claims[i]);
        await a.deploy({
            fee: {
                paymentMethod
            }
        }).wait();
        logger.info(`Account deployed at ${a.getAddress()}`);
        return wallet;
    }));
    const tokenAdmin = wallets[0];
    const tokenAddress = await deployTokenAndMint(wallets, tokenAdmin.getAddress(), mintAmount, undefined, logger);
    const tokenAdminWallet = await TokenContract.at(tokenAddress, tokenAdmin);
    return {
        pxe,
        wallets,
        tokenAdminAddress: tokenAdmin.getAddress(),
        tokenAdminWallet,
        tokenName: TOKEN_NAME,
        tokenAddress,
        recipientWallet,
        recipientAddress: recipientWallet.getAddress()
    };
}
async function bridgeL1FeeJuice(l1RpcUrls, mnemonicOrPrivateKey, pxe, recipient, amount, log) {
    const { l1ChainId } = await pxe.getNodeInfo();
    const chain = createEthereumChain(l1RpcUrls, l1ChainId);
    const l1Client = createExtendedL1Client(chain.rpcUrls, mnemonicOrPrivateKey, chain.chainInfo);
    // docs:start:bridge_fee_juice
    const portal = await L1FeeJuicePortalManager.new(pxe, l1Client, log);
    const claim = await portal.bridgeTokensPublic(recipient, amount, true);
    // docs:end:bridge_fee_juice
    const isSynced = async ()=>await pxe.isL1ToL2MessageSynced(Fr.fromHexString(claim.messageHash));
    await retryUntil(isSynced, `message ${claim.messageHash} sync`, 24, 0.5);
    log.info(`Created a claim for ${amount} L1 fee juice to ${recipient}.`, claim);
    return claim;
}
async function advanceL2Block(node, nodeAdmin) {
    const initialBlockNumber = await node.getBlockNumber();
    let minTxsPerBlock = undefined;
    if (nodeAdmin) {
        ({ minTxsPerBlock } = await nodeAdmin.getConfig());
        await nodeAdmin.setConfig({
            minTxsPerBlock: 0
        }); // Set to 0 to ensure we can advance the block
    }
    await retryUntil(async ()=>await node.getBlockNumber() >= initialBlockNumber + 1);
    if (nodeAdmin && minTxsPerBlock !== undefined) {
        await nodeAdmin.setConfig({
            minTxsPerBlock
        });
    }
}
async function deployTokenAndMint(wallets, admin, mintAmount, paymentMethod, logger) {
    logger.verbose(`Deploying TokenContract...`);
    const tokenContract = await TokenContract.deploy(wallets[0], admin, TOKEN_NAME, TOKEN_SYMBOL, TOKEN_DECIMALS).send({
        from: admin,
        fee: {
            paymentMethod
        }
    }).deployed({
        timeout: 600
    });
    const tokenAddress = tokenContract.address;
    logger.verbose(`Minting ${mintAmount} public assets to the ${wallets.length} wallets...`);
    await Promise.all(wallets.map(async (w)=>(await TokenContract.at(tokenAddress, w)).methods.mint_to_public(w.getAddress(), mintAmount).send({
            from: admin,
            fee: {
                paymentMethod
            }
        }).wait({
            timeout: 600
        })));
    logger.verbose(`Minting complete.`);
    return tokenAddress;
}
export async function performTransfers({ testWallets, rounds, transferAmount, logger }) {
    const recipient = testWallets.recipientWallet.getAddress();
    for(let i = 0; i < rounds; i++){
        const txs = testWallets.wallets.map(async (w)=>(await TokenContract.at(testWallets.tokenAddress, w)).methods.transfer_in_public(w.getAddress(), recipient, transferAmount, 0).prove({
                from: w.getAddress()
            }));
        const provenTxs = await Promise.all(txs);
        await Promise.all(provenTxs.map((t)=>t.send().wait({
                timeout: 600
            })));
        logger.info(`Completed round ${i + 1} / ${rounds}`);
    }
}
export async function startCompatiblePXE(nodeUrl, proverEnabled, logger) {
    const node = createAztecNodeClient(nodeUrl);
    const [bbConfig, acvmConfig] = await Promise.all([
        getBBConfig(logger),
        getACVMConfig(logger)
    ]);
    const pxe = await createPXEService(node, {
        dataDirectory: undefined,
        dataStoreMapSizeKb: 1024 * 1024,
        ...bbConfig,
        ...acvmConfig,
        proverEnabled
    });
    return {
        pxe,
        async cleanup () {
            await pxe.stop();
            await bbConfig?.cleanup();
            await acvmConfig?.cleanup();
        }
    };
}
