// 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 {ValidatorSigVerifierExtension} from "../../contracts/mainnet-checkpoint/extensions/ValidatorSigVerifierExtension.sol";
import {TokenTransferFilterExtension} from "../../contracts/mainnet-checkpoint/extensions/TokenTransferFilterExtension.sol";
import {ExtensionIds} from "../../contracts/mainnet-checkpoint/libraries/ExtensionIds.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 {CheckpointEIP712} from "../../contracts/mainnet-checkpoint/libraries/CheckpointEIP712.sol";
import {ICheckpointExtension} from "../../contracts/mainnet-checkpoint/interfaces/ICheckpointExtension.sol";

/// @notice Hub v3 passes validatorSignatures to VALIDATOR_SIG and leaf bytes to TOKEN_FILTER.
contract ExtensionHookWiringTest is Test {
    Chain138MainnetCheckpoint hub;
    ValidatorSigVerifierExtension validator;
    uint256 validatorPk = 0xA11CE;
    address validatorAddr;
    address submitter = address(0xB0B);

    function setUp() public {
        validatorAddr = vm.addr(validatorPk);
        Chain138MainnetCheckpoint impl = new Chain138MainnetCheckpoint();
        bytes memory initData = abi.encodeCall(
            Chain138MainnetCheckpoint.initialize,
            (address(this), address(1), uint64(1), address(0))
        );
        hub = Chain138MainnetCheckpoint(address(new ERC1967Proxy(address(impl), initData)));

        validator = new ValidatorSigVerifierExtension();
        TokenTransferFilterExtension tokenFilter = new TokenTransferFilterExtension();
        tokenFilter.setAllowNative(true, 0);

        validator.setVerifyingContract(address(hub));
        address[] memory addrs = new address[](1);
        addrs[0] = validatorAddr;
        validator.setValidators(addrs, 1);

        _register(ExtensionIds.VALIDATOR_SIG, address(validator));
        _register(ExtensionIds.TOKEN_FILTER, address(tokenFilter));

        CheckpointHubConfig.HubConfig memory cfg = CheckpointHubConfig.mainnetDefaults();
        cfg.enforcePreviousBatchId = false;
        cfg.requireValidatorSigs = true;
        hub.applyConfig(cfg);
        hub.grantRole(hub.SUBMITTER_ROLE(), submitter);
    }

    function testSubmitWithLeavesAndValidatorSig() public {
        CheckpointLeaf.PaymentLeafV1[] memory leaves = new CheckpointLeaf.PaymentLeafV1[](1);
        leaves[0] = CheckpointLeaf.PaymentLeafV1({
            txHash: keccak256("tx"),
            from: address(3),
            to: address(4),
            value: 1,
            blockNumber: 10,
            blockTimestamp: 1,
            gasUsed: 1,
            success: true
        });
        bytes32[] memory hashes = new bytes32[](1);
        hashes[0] = CheckpointLeaf.paymentLeafV1(138, leaves[0]);
        bytes32 root = CheckpointLeaf.buildMerkleRoot(hashes);

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

        bytes32 digest = _validatorDigest(address(hub), block.chainid, header);
        (uint8 v, bytes32 r, bytes32 s) = vm.sign(validatorPk, digest);
        bytes memory sig = abi.encodePacked(r, s, v);

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

        vm.prank(submitter);
        hub.submitCheckpointWithLeaves(header, sig, txHashes, leaves);
        assertEq(hub.getLatestBatchId(), 1);
    }

    function _validatorDigest(
        address verifyingContract,
        uint256 chainId,
        CheckpointStorage.CheckpointHeader memory header
    ) internal pure returns (bytes32) {
        bytes32 structHash = keccak256(
            abi.encode(
                CheckpointEIP712.BATCH_ATTESTATION_TYPEHASH,
                header.chainId,
                header.batchId,
                header.checkpointBlock,
                header.blockHash,
                header.stateRoot,
                header.paymentsRoot,
                header.previousBatchId
            )
        );
        bytes32 domain = keccak256(
            abi.encode(
                CheckpointEIP712.DOMAIN_TYPEHASH,
                keccak256("Chain138MainnetCheckpoint"),
                keccak256("2"),
                chainId,
                verifyingContract
            )
        );
        return keccak256(abi.encodePacked("\x19\x01", domain, structHash));
    }

    function _register(bytes32 id, address module) internal {
        uint32 hooks = ICheckpointExtension(module).HOOK_BEFORE_SUBMIT()
            | ICheckpointExtension(module).HOOK_AFTER_SUBMIT()
            | ICheckpointExtension(module).HOOK_ON_CCIP()
            | ICheckpointExtension(module).HOOK_VERIFY_LEAF();
        hub.registerExtension(id, module, hooks);
    }
}
