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

import "forge-std/Test.sol";
import "../../contracts/dbis/IDBISTypes.sol";
import "../../contracts/dbis/DBIS_RootRegistry.sol";
import "../../contracts/dbis/DBIS_ParticipantRegistry.sol";
import "../../contracts/dbis/DBIS_SignerRegistry.sol";
import "../../contracts/dbis/DBIS_GRU_MintController.sol";
import "../../contracts/dbis/DBIS_EIP712Helper.sol";
import "../../contracts/dbis/DBIS_SettlementRouter.sol";
import "../../contracts/dbis/StablecoinReferenceRegistry.sol";
import "./MockMintableToken.sol";

contract DBIS_RailTest is Test, IDBISTypes {
    DBIS_RootRegistry public root;
    DBIS_ParticipantRegistry public participantReg;
    DBIS_SignerRegistry public signerReg;
    DBIS_GRU_MintController public mintController;
    DBIS_SettlementRouter public router;
    StablecoinReferenceRegistry public stableReg;
    MockMintableToken public token;

    address public admin;
    address public alice;
    address public signer1;
    address public signer2;
    address public signer3;
    uint256 signer1Key;
    uint256 signer2Key;
    uint256 signer3Key;

    function setUp() public {
        admin = makeAddr("admin");
        alice = makeAddr("alice");
        (signer1, signer1Key) = makeAddrAndKey("signer1");
        (signer2, signer2Key) = makeAddrAndKey("signer2");
        (signer3, signer3Key) = makeAddrAndKey("signer3");

        vm.startPrank(admin);
        root = new DBIS_RootRegistry(admin, "v1");
        participantReg = new DBIS_ParticipantRegistry(admin);
        signerReg = new DBIS_SignerRegistry(admin);
        mintController = new DBIS_GRU_MintController(admin, address(0));
        router = new DBIS_SettlementRouter(admin, address(root), address(new DBIS_EIP712Helper()));
        stableReg = new StablecoinReferenceRegistry(admin);

        root.setComponent(keccak256("ParticipantRegistry"), address(participantReg));
        root.setComponent(keccak256("SignerRegistry"), address(signerReg));
        root.setComponent(keccak256("GRUMintController"), address(mintController));
        mintController.setSettlementRouter(address(router));

        token = new MockMintableToken("Test GRU", "tGRU", 6, admin);
        mintController.setGruToken(address(token));
        token.grantRole(token.MINTER_ROLE(), address(mintController));

        signerReg.addSigner(signer1, 0);
        signerReg.addSigner(signer2, 1);
        signerReg.addSigner(signer3, 2);

        address[] memory wallets = new address[](1);
        wallets[0] = alice;
        bytes32[] memory tags;
        participantReg.registerParticipant(DBIS_ParticipantRegistry.Participant({
            participantId: keccak256("participant1"),
            legalName: "Test FI",
            jurisdiction: "US",
            entityType: DBIS_ParticipantRegistry.EntityType.BANK,
            status: DBIS_ParticipantRegistry.Status.ACTIVE,
            policyTags: tags,
            operationalWallets: wallets
        }));

        vm.stopPrank();
    }

    function test_submitMintAuth_success() public {
        address[] memory recipients = new address[](1);
        recipients[0] = alice;
        uint256[] memory amounts = new uint256[](1);
        amounts[0] = 1000 * 1e6;

        MintAuth memory auth = MintAuth({
            messageId: keccak256("msg1"),
            isoType: keccak256("pacs.008"),
            isoHash: keccak256("isobundle"),
            accountingRef: keccak256("acctref"),
            fundsStatus: FundsStatus.ON_LEDGER_FINAL,
            corridor: bytes32(0),
            assetClass: AssetClass.GRU_M00,
            recipients: recipients,
            amounts: amounts,
            notBefore: uint64(block.timestamp - 1),
            expiresAt: uint64(block.timestamp + 300),
            chainId: 138,
            verifyingContract: address(router)
        });

        bytes32 digest = router.getMintAuthDigest(auth);
        bytes memory sig1 = _sign(digest, signer1Key);
        bytes memory sig2 = _sign(digest, signer2Key);
        bytes memory sig3 = _sign(digest, signer3Key);
        bytes[] memory sigs = new bytes[](3);
        sigs[0] = sig1;
        sigs[1] = sig2;
        sigs[2] = sig3;

        vm.prank(alice);
        router.submitMintAuth(auth, sigs);

        assertEq(token.balanceOf(alice), 1000 * 1e6);
        assertTrue(router.usedMessageIds(auth.messageId));
    }

    function test_submitMintAuth_replayReverts() public {
        address[] memory recipients = new address[](1);
        recipients[0] = alice;
        uint256[] memory amounts = new uint256[](1);
        amounts[0] = 1000 * 1e6;
        MintAuth memory auth = MintAuth({
            messageId: keccak256("msg2"),
            isoType: keccak256("pacs.008"),
            isoHash: keccak256("isobundle"),
            accountingRef: keccak256("acctref"),
            fundsStatus: FundsStatus.ON_LEDGER_FINAL,
            corridor: bytes32(0),
            assetClass: AssetClass.GRU_M00,
            recipients: recipients,
            amounts: amounts,
            notBefore: uint64(block.timestamp - 1),
            expiresAt: uint64(block.timestamp + 300),
            chainId: 138,
            verifyingContract: address(router)
        });
        bytes32 digest = router.getMintAuthDigest(auth);
        bytes[] memory sigs = new bytes[](3);
        sigs[0] = _sign(digest, signer1Key);
        sigs[1] = _sign(digest, signer2Key);
        sigs[2] = _sign(digest, signer3Key);

        vm.prank(alice);
        router.submitMintAuth(auth, sigs);
        vm.expectRevert("DBIS: replay");
        vm.prank(alice);
        router.submitMintAuth(auth, sigs);
    }

    function test_signerActiveAtBlock_afterRevoke() public {
        vm.prank(admin);
        signerReg.revokeSignerAtBlock(signer1);
        address[] memory recipients = new address[](1);
        recipients[0] = alice;
        uint256[] memory amounts = new uint256[](1);
        amounts[0] = 1000 * 1e6;
        MintAuth memory auth = MintAuth({
            messageId: keccak256("msg3"),
            isoType: keccak256("pacs.008"),
            isoHash: keccak256("isobundle"),
            accountingRef: keccak256("acctref"),
            fundsStatus: FundsStatus.ON_LEDGER_FINAL,
            corridor: bytes32(0),
            assetClass: AssetClass.GRU_M00,
            recipients: recipients,
            amounts: amounts,
            notBefore: uint64(block.timestamp - 1),
            expiresAt: uint64(block.timestamp + 300),
            chainId: 138,
            verifyingContract: address(router)
        });
        bytes32 digest = router.getMintAuthDigest(auth);
        bytes[] memory sigs = new bytes[](3);
        sigs[0] = _sign(digest, signer1Key);
        sigs[1] = _sign(digest, signer2Key);
        sigs[2] = _sign(digest, signer3Key);
        vm.prank(alice);
        vm.expectRevert("DBIS: signer not active at block");
        router.submitMintAuth(auth, sigs);
    }

    function _sign(bytes32 digest, uint256 pk) internal pure returns (bytes memory) {
        (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk, digest);
        return abi.encodePacked(r, s, v);
    }
}
