import * as dotenv from 'dotenv';
import { encodeFunctionData, Hex, parseAbi, parseUnits } from 'viem';
import { EtherspotBundler, RemoteSignerSdk, UserOperation, toRemoteSigner, BigNumber, createLocalAccount, ExtendedLocalAccount, printOp } from '@etherspot/remote-signer';
import { privateKeyToAccount } from 'viem/accounts';

dotenv.config();

const tokenAddress = process.env.TOKEN_ADDRESS as string; // token address
const bundlerApiKey = process.env.API_KEY as string; // bundlerApiKey
const sessionKey = process.env.SESSION_KEY as string; // user's sessionKey
const erc20SessionKeyValidator = process.env.ERC20_SESSION_KEY_VALIDATOR as string;
const apiKey = process.env.API_KEY as string;
const etherspotWalletAddress = process.env.ETHERSPOT_WALLET_ADDRESS as string;
const chainId = Number(process.env.CHAIN_ID);
const recipient = '0xdE79F0eF8A1268DAd0Df02a8e527819A3Cd99d40'; // recipient wallet address
const value = '1'; // transfer value
let remoteSigner: ExtendedLocalAccount;

async function main() {
    remoteSigner = await toRemoteSigner({
        account: createLocalAccount(etherspotWalletAddress),
        chainId: chainId,
        apiKey: apiKey,
        sessionKey: sessionKey
    });

    const op = await generateSignedUserOp();
    const signedUserOp: UserOperation = await remoteSigner.signUserOpWithRemoteSigner(op);
    const signedUserOp_Printable = await printOp(signedUserOp);
    console.log(`Signed UserOp: ${signedUserOp_Printable} generated via remote-signer`);
}

async function generateSignedUserOp() {
    const externalViemAccount = privateKeyToAccount(process.env.WALLET_PRIVATE_KEY as string as Hex);
    const bundlerProvider = new EtherspotBundler(chainId, bundlerApiKey);
    const remoteSignerSdk = await RemoteSignerSdk.create(externalViemAccount, {
        etherspotWalletAddress: etherspotWalletAddress,
        chainId: chainId,
        apiKey: apiKey,
        sessionKey: sessionKey,
        bundlerProvider: bundlerProvider
    });
    const transactionData = await getTransferERC20Data(remoteSignerSdk.getPublicClient());

    // clear the transaction batch
    await remoteSignerSdk.clearUserOpsFromBatch();

    // add transactions to the batch
    const userOpsBatch = await remoteSignerSdk.addUserOpsToBatch({ to: tokenAddress, data: transactionData });

    if (userOpsBatch.data.length === 0) {
        throw new Error('No user operations added to the batch');
    }

    let nonceKey = BigNumber.from(erc20SessionKeyValidator);

    // estimate transactions added to the batch and get the fee data for the UserOp
    const op = await remoteSignerSdk.estimate({ nonceKey: nonceKey });

    const signedUserOp = await remoteSdkSigner.signUserOp(op);

    return signedUserOp;
}

const erc20Abi = [
  'function approve(address spender, uint256 value) returns (bool)',
  'function decimals() view returns (uint8)',
  'function name() view returns (string)',
  'function symbol() view returns (string)',
  'function totalSupply() returns (uint256)',
  'function balanceOf(address account) returns (uint256)',
  'function allowance(address owner, address spender) returns (uint256)',
  'function transfer(address to, uint256 value) returns (bool)',
  'function transferFrom(address from, address to, uint256 value) returns (bool)'
];

async function getTransferERC20Data(publicClient) {
    const decimals = await publicClient.readContract({
        address: tokenAddress as Hex,
        abi: parseAbi(erc20Abi),
        functionName: 'decimals',
        args: []
    });

    // get transferFrom encoded data
    const transactionData = encodeFunctionData({
        functionName: 'transfer',
        abi: parseAbi(erc20Abi),
        args: [recipient, parseUnits(value, decimals as number)]
    });
    return transactionData;
}