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

import {Test} from "forge-std/Test.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import {Chain138MainnetCheckpoint} from "../../contracts/mainnet-checkpoint/Chain138MainnetCheckpoint.sol";
import {CheckpointStorage} from "../../contracts/mainnet-checkpoint/storage/CheckpointStorage.sol";
import {CheckpointLeaf} from "../../contracts/mainnet-checkpoint/libraries/CheckpointLeaf.sol";
import {CheckpointFlags} from "../../contracts/mainnet-checkpoint/libraries/CheckpointFlags.sol";
import {CheckpointHubConfig} from "../../contracts/mainnet-checkpoint/libraries/CheckpointHubConfig.sol";
import {CheckpointErrors} from "../../contracts/mainnet-checkpoint/libraries/CheckpointErrors.sol";

contract SubmitWithLeavesTest is Test {
    Chain138MainnetCheckpoint hub;
    address admin = address(0xA11CE);
    address submitter = address(0xB0B);

    function setUp() public {
        Chain138MainnetCheckpoint impl = new Chain138MainnetCheckpoint();
        bytes memory initData = abi.encodeCall(
            Chain138MainnetCheckpoint.initialize,
            (admin, address(1), uint64(1), address(0))
        );
        hub = Chain138MainnetCheckpoint(address(new ERC1967Proxy(address(impl), initData)));
        vm.startPrank(admin);
        hub.grantRole(hub.SUBMITTER_ROLE(), submitter);
        CheckpointHubConfig.HubConfig memory cfg = CheckpointHubConfig.mainnetDefaults();
        cfg.enforcePreviousBatchId = false;
        cfg.requireValidatorSigs = false;
        hub.applyConfig(cfg);
        vm.stopPrank();
    }

    function testSubmitWithLeavesVerifiesRoot() public {
        CheckpointLeaf.PaymentLeafV1[] memory leaves = new CheckpointLeaf.PaymentLeafV1[](2);
        leaves[0] = _leaf(keccak256("a"));
        leaves[1] = _leaf(keccak256("b"));
        bytes32[] memory hashes = new bytes32[](2);
        hashes[0] = CheckpointLeaf.paymentLeafV1(138, leaves[0]);
        hashes[1] = CheckpointLeaf.paymentLeafV1(138, leaves[1]);
        bytes32 root = CheckpointLeaf.buildMerkleRoot(hashes);

        CheckpointStorage.CheckpointHeader memory header = CheckpointStorage.CheckpointHeader({
            batchId: 1,
            previousBatchId: 0,
            chainId: 138,
            checkpointBlock: 10,
            startBlock: 9,
            endBlock: 10,
            blockHash: keccak256("bh"),
            stateRoot: keccak256("sr"),
            paymentsRoot: root,
            receiptsRoot: bytes32(0),
            txCount: 2,
            flags: CheckpointFlags.PARTIAL_BATCH,
            submittedAt: 0,
            submitter: address(0),
            contentURI: bytes32(0)
        });

        bytes32[] memory txHashes = new bytes32[](2);
        txHashes[0] = leaves[0].txHash;
        txHashes[1] = leaves[1].txHash;

        vm.prank(submitter);
        hub.submitCheckpointWithLeaves(header, hex"01", txHashes, leaves);

        assertEq(hub.getLatestBatchId(), 1);
    }

    function testSubmitWithLeavesRevertsOnBadRoot() public {
        CheckpointLeaf.PaymentLeafV1[] memory leaves = new CheckpointLeaf.PaymentLeafV1[](1);
        leaves[0] = _leaf(keccak256("x"));
        CheckpointStorage.CheckpointHeader memory header = CheckpointStorage.CheckpointHeader({
            batchId: 1,
            previousBatchId: 0,
            chainId: 138,
            checkpointBlock: 1,
            startBlock: 1,
            endBlock: 1,
            blockHash: keccak256("h"),
            stateRoot: keccak256("s"),
            paymentsRoot: keccak256("wrong"),
            receiptsRoot: bytes32(0),
            txCount: 1,
            flags: CheckpointFlags.PARTIAL_BATCH,
            submittedAt: 0,
            submitter: address(0),
            contentURI: bytes32(0)
        });
        vm.prank(submitter);
        vm.expectRevert(CheckpointErrors.RootMismatch.selector);
        hub.submitCheckpointWithLeaves(header, hex"01", new bytes32[](0), leaves);
    }

    function _leaf(bytes32 txHash) private pure returns (CheckpointLeaf.PaymentLeafV1 memory) {
        return CheckpointLeaf.PaymentLeafV1({
            txHash: txHash,
            from: address(3),
            to: address(4),
            value: 1,
            blockNumber: 1,
            blockTimestamp: 1,
            gasUsed: 1,
            success: true
        });
    }
}
