// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Test} from "forge-std/Test.sol";
import {AccountERC7702Mock} from "@openzeppelin/contracts/mocks/account/AccountMock.sol";
import {CallReceiverMock} from "@openzeppelin/contracts/mocks/CallReceiverMock.sol";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import {
    ERC7579Utils,
    Execution,
    Mode,
    ModeSelector,
    ModePayload
} from "@openzeppelin/contracts/account/utils/draft-ERC7579Utils.sol";
import {ERC4337Utils, IEntryPointExtra} from "@openzeppelin/contracts/account/utils/draft-ERC4337Utils.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {PackedUserOperation} from "@openzeppelin/contracts/interfaces/draft-IERC4337.sol";
import {ERC7821} from "@openzeppelin/contracts/account/extensions/draft-ERC7821.sol";

contract AccountERC7702MockConstructor is AccountERC7702Mock {
    constructor() EIP712("MyAccount", "1") {}
}

contract AccountERC7702Test is Test {
    using ERC7579Utils for *;
    using ERC4337Utils for PackedUserOperation;
    using Strings for *;

    uint256 private constant MAX_ETH = type(uint128).max;

    // Test accounts
    CallReceiverMock private _target;

    // ERC-4337 signer
    uint256 private _signerPrivateKey;
    AccountERC7702MockConstructor private _signer;

    function setUp() public {
        // Deploy target contract
        _target = new CallReceiverMock();

        // Setup signer
        _signerPrivateKey = 0x1234;
        _signer = AccountERC7702MockConstructor(payable(vm.addr(_signerPrivateKey)));
        vm.deal(address(_signer), MAX_ETH);

        // Sign and attach delegation
        vm.signAndAttachDelegation(address(new AccountERC7702MockConstructor()), _signerPrivateKey);

        // Setup entrypoint
        vm.deal(address(ERC4337Utils.ENTRYPOINT_V08), MAX_ETH);
        vm.etch(address(ERC4337Utils.ENTRYPOINT_V08), vm.readFileBinary("test/bin/EntryPoint070.bytecode"));
    }

    function testExecuteBatch(uint256 argA, uint256 argB) public {
        // Create the mode for batch execution
        Mode mode = ERC7579Utils.CALLTYPE_BATCH.encodeMode(
            ERC7579Utils.EXECTYPE_DEFAULT,
            ModeSelector.wrap(0x00000000),
            ModePayload.wrap(0x00000000)
        );

        Execution[] memory execution = new Execution[](2);
        execution[0] = Execution({
            target: address(_target),
            value: 1 ether,
            callData: abi.encodeCall(CallReceiverMock.mockFunctionExtra, ())
        });
        execution[1] = Execution({
            target: address(_target),
            value: 0,
            callData: abi.encodeCall(CallReceiverMock.mockFunctionWithArgs, (argA, argB))
        });

        // Pack the batch within a PackedUserOperation
        PackedUserOperation[] memory ops = new PackedUserOperation[](1);
        ops[0] = PackedUserOperation({
            sender: address(_signer),
            nonce: 0,
            initCode: bytes(""),
            callData: abi.encodeCall(ERC7821.execute, (Mode.unwrap(mode), execution.encodeBatch())),
            preVerificationGas: 100000,
            accountGasLimits: bytes32(abi.encodePacked(uint128(100000), uint128(100000))),
            gasFees: bytes32(abi.encodePacked(uint128(1000000), uint128(1000000))),
            paymasterAndData: bytes(""),
            signature: bytes("")
        });
        (uint8 v, bytes32 r, bytes32 s) = vm.sign(
            _signerPrivateKey,
            IEntryPointExtra(address(ERC4337Utils.ENTRYPOINT_V08)).getUserOpHash(ops[0])
        );
        ops[0].signature = abi.encodePacked(r, s, v);

        // Expect the events to be emitted
        vm.expectEmit(true, true, true, true);
        emit CallReceiverMock.MockFunctionCalledExtra(address(_signer), 1 ether);
        vm.expectEmit(true, true, true, true);
        emit CallReceiverMock.MockFunctionCalledWithArgs(argA, argB);

        // Execute the batch
        _signer.entryPoint().handleOps(ops, payable(makeAddr("beneficiary")));
    }
}
