import {
  EntrypointAbi,
  createSmartAccountClient,
  toNexusAccount
} from "@biconomy/abstractjs"
import {
  http,
  type Address,
  encodeFunctionData,
  erc20Abi,
  parseEther
} from "viem"
import { toPackedUserOperation } from "viem/account-abstraction"
import {
  ENTRYPOINT_V07_ADDRESS,
  FREE_MINT_ERC20,
  NEXUS_IMPLEMENTATION_ADDRESS,
  getBalance,
  toClients
} from "../src"
import type { Infra } from "../src/toEcosystem"

export const benchmark7702 = async ({
  bundler,
  network: { rpcUrl, chain }
}: Infra) => {
  const { publicClient, walletClients, testClient, accounts } = await toClients(
    {
      rpcUrl,
      chain
    }
  )

  const nexusAccount = await toNexusAccount({
    signer: accounts[0],
    chain,
    transport: http(rpcUrl)
  })

  const nexusAccountClient = createSmartAccountClient({
    account: nexusAccount,
    chain,
    transport: http(bundler.url),
    mock: true
  })

  await testClient.setBalance({
    address: nexusAccount.address,
    value: parseEther("10")
  })

  const eip7702Account = accounts[8]
  const someWallet = accounts[9]

  const eip7702AccountClient = walletClients[8]
  const someWalletClient = walletClients[9]

  const userOp = await nexusAccountClient.prepareUserOperation({
    calls: [
      {
        to: FREE_MINT_ERC20,
        value: 0n,
        data: encodeFunctionData({
          abi: erc20Abi,
          functionName: "transfer",
          args: [someWallet.address, parseEther("1")]
        })
      }
    ]
  })

  // update sender to be the eip7702Account address
  userOp.sender = eip7702Account.address
  userOp.factory = "0x"
  userOp.factoryData = "0x"

  const updatedHash = nexusAccount.getUserOpHash(userOp)

  const sig = await eip7702Account.signMessage({
    message: { raw: updatedHash }
  })
  userOp.signature = sig

  const nonce = await publicClient.getTransactionCount({
    address: eip7702Account.address
  })

  // sign eip7702 authorization with viem
  const authorization = await eip7702AccountClient.prepareAuthorization({
    account: eip7702Account,
    contractAddress: NEXUS_IMPLEMENTATION_ADDRESS,
    chainId: 0,
    nonce: nonce
  })
  const signedAuthorization =
    await eip7702AccountClient.signAuthorization(authorization)

  const packedUserOp = toPackedUserOperation(userOp)
  const balanceBefore = await getBalance(
    publicClient,
    someWallet.address,
    FREE_MINT_ERC20
  )

  const handleOpsHash = await someWalletClient.sendTransaction({
    authorizationList: [signedAuthorization],
    data: encodeFunctionData({
      abi: EntrypointAbi,
      functionName: "handleOps",
      args: [[packedUserOp], eip7702Account.address]
    }),
    to: ENTRYPOINT_V07_ADDRESS
  })

  // get the receipt
  const receipt = await publicClient.waitForTransactionReceipt({
    hash: handleOpsHash
  })
  const balanceAfter = await getBalance(
    publicClient,
    someWallet.address,
    FREE_MINT_ERC20
  )

  if (balanceAfter - balanceBefore !== parseEther("1")) {
    throw new Error("Balance has not changed properly")
  }
  console.log("Delegate EOA to Nexus via 7702 + send ERC20", receipt.gasUsed)
}
