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

import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "./IDBISTypes.sol";
import "./IDBIS_EIP712Helper.sol";
import "./DBIS_RootRegistry.sol";
import "./DBIS_ParticipantRegistry.sol";
import "./DBIS_SignerRegistry.sol";
import "./DBIS_GRU_MintController.sol";

contract DBIS_SettlementRouter is AccessControl, Pausable, ReentrancyGuard {
    bytes32 public constant ROUTER_ADMIN_ROLE = keccak256("ROUTER_ADMIN");

    uint256 public constant CHAIN_ID = 138;

    bytes32 public constant PARTICIPANT_REGISTRY_KEY = keccak256("ParticipantRegistry");
    bytes32 public constant SIGNER_REGISTRY_KEY = keccak256("SignerRegistry");
    bytes32 public constant GRU_MINT_CONTROLLER_KEY = keccak256("GRUMintController");

    bytes32 private constant EIP712_DOMAIN_TYPEHASH = keccak256(
        "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
    );
    bytes32 private constant MINTAUTH_TYPEHASH = keccak256(
        "MintAuth(bytes32 messageId,bytes32 isoType,bytes32 isoHash,bytes32 accountingRef,uint8 fundsStatus,bytes32 corridor,uint8 assetClass,bytes32 recipientsHash,bytes32 amountsHash,uint64 notBefore,uint64 expiresAt,uint256 chainId,address verifyingContract)"
    );

    DBIS_RootRegistry public rootRegistry;
    address public eip712Lib;
    mapping(bytes32 => bool) public usedMessageIds;
    uint256 public maxAmountPerMessage;
    mapping(bytes32 => uint256) public corridorDailyCap;
    mapping(bytes32 => mapping(uint256 => uint256)) public corridorUsedToday;

    event SettlementRecorded(
        bytes32 indexed messageId,
        bytes32 indexed isoType,
        bytes32 isoHash,
        bytes32 accountingRef,
        uint8 fundsStatus,
        bytes32 corridor,
        uint8 assetClass,
        uint256 totalAmount
    );
    event MintExecuted(bytes32 indexed messageId, address indexed recipient, uint256 amount, uint8 assetClass);
    event MessageIdConsumed(bytes32 indexed messageId);
    event RouterPaused(bool paused);

    constructor(address admin, address _rootRegistry, address _eip712Lib) {
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(ROUTER_ADMIN_ROLE, admin);
        rootRegistry = DBIS_RootRegistry(_rootRegistry);
        eip712Lib = _eip712Lib;
        maxAmountPerMessage = type(uint256).max;
    }

    function setMaxAmountPerMessage(uint256 cap) external onlyRole(ROUTER_ADMIN_ROLE) {
        maxAmountPerMessage = cap;
    }

    function setCorridorDailyCap(bytes32 corridor, uint256 cap) external onlyRole(ROUTER_ADMIN_ROLE) {
        corridorDailyCap[corridor] = cap;
    }

    function pause() external onlyRole(ROUTER_ADMIN_ROLE) {
        _pause();
        emit RouterPaused(true);
    }

    function unpause() external onlyRole(ROUTER_ADMIN_ROLE) {
        _unpause();
        emit RouterPaused(false);
    }

    function _domainSeparator() private view returns (bytes32) {
        return keccak256(
            abi.encode(
                EIP712_DOMAIN_TYPEHASH,
                keccak256("DBISSettlementRouter"),
                keccak256("1"),
                CHAIN_ID,
                address(this)
            )
        );
    }

    function _hashMintAuth(IDBISTypes.MintAuth calldata auth) private view returns (bytes32) {
        IDBIS_EIP712Helper helper = IDBIS_EIP712Helper(eip712Lib);
        bytes32 rh = helper.hashAddressArray(auth.recipients);
        bytes32 ah = helper.hashUint256Array(auth.amounts);
        return helper.getMintAuthStructHash(
            MINTAUTH_TYPEHASH,
            auth.messageId,
            auth.isoType,
            auth.isoHash,
            auth.accountingRef,
            uint8(auth.fundsStatus),
            auth.corridor,
            uint8(auth.assetClass),
            rh,
            ah,
            auth.notBefore,
            auth.expiresAt,
            auth.chainId,
            auth.verifyingContract
        );
    }

    function getMintAuthDigest(IDBISTypes.MintAuth calldata auth) external view returns (bytes32) {
        return IDBIS_EIP712Helper(eip712Lib).getDigest(_domainSeparator(), _hashMintAuth(auth));
    }

    function submitMintAuth(IDBISTypes.MintAuth calldata auth, bytes[] calldata signatures) external nonReentrant whenNotPaused {
        require(auth.chainId == CHAIN_ID, "DBIS: wrong chain");
        require(auth.verifyingContract == address(this), "DBIS: wrong contract");
        require(block.timestamp >= auth.notBefore && block.timestamp <= auth.expiresAt, "DBIS: time window");
        require(!usedMessageIds[auth.messageId], "DBIS: replay");
        require(auth.recipients.length == auth.amounts.length, "DBIS: length mismatch");
        require(auth.recipients.length > 0, "DBIS: no recipients");

        uint256 totalAmount = _sumAmounts(auth.amounts);
        require(totalAmount <= maxAmountPerMessage, "DBIS: cap exceeded");

        uint256 day = block.timestamp / 1 days;
        require(_checkCorridorCap(auth.corridor, day, totalAmount), "DBIS: corridor cap");

        _requireRecipientsOperational(auth.recipients);
        _recoverSigners(auth, signatures);

        usedMessageIds[auth.messageId] = true;
        corridorUsedToday[auth.corridor][day] = corridorUsedToday[auth.corridor][day] + totalAmount;

        _emitSettlementEvents(auth, totalAmount);
        DBIS_GRU_MintController mintController = DBIS_GRU_MintController(rootRegistry.getComponent(GRU_MINT_CONTROLLER_KEY));
        require(address(mintController) != address(0), "DBIS: no mint controller");
        mintController.mintFromAuthorization(auth);
    }

    function _sumAmounts(uint256[] calldata amounts) private pure returns (uint256) {
        uint256 total = 0;
        for (uint256 i = 0; i < amounts.length; i++) total += amounts[i];
        return total;
    }

    function _checkCorridorCap(bytes32 corridor, uint256 day, uint256 totalAmount) private view returns (bool) {
        uint256 cap = corridorDailyCap[corridor];
        if (cap == 0) return true;
        return corridorUsedToday[corridor][day] + totalAmount <= cap;
    }

    function _requireRecipientsOperational(address[] calldata recipients) private view {
        DBIS_ParticipantRegistry participantReg = DBIS_ParticipantRegistry(rootRegistry.getComponent(PARTICIPANT_REGISTRY_KEY));
        require(address(participantReg) != address(0), "DBIS: no participant registry");
        for (uint256 i = 0; i < recipients.length; i++) {
            require(participantReg.isOperationalWallet(recipients[i]), "DBIS: not operational wallet");
        }
    }

    function _recoverSigners(IDBISTypes.MintAuth calldata auth, bytes[] calldata signatures) private view returns (address[] memory signers) {
        DBIS_SignerRegistry signerReg = DBIS_SignerRegistry(rootRegistry.getComponent(SIGNER_REGISTRY_KEY));
        require(address(signerReg) != address(0), "DBIS: no signer registry");
        IDBIS_EIP712Helper helper = IDBIS_EIP712Helper(eip712Lib);
        bytes32 digest = helper.getDigest(_domainSeparator(), _hashMintAuth(auth));
        signers = helper.recoverSigners(digest, signatures);
        require(!signerReg.hasDuplicateSigners(signers), "DBIS: duplicate signer");
        require(signerReg.areSignersActiveAtBlock(signers, block.number), "DBIS: signer not active at block");
        (bool ok, ) = signerReg.validateSigners(signers);
        require(ok, "DBIS: quorum not met");
    }

    function _emitSettlementEvents(IDBISTypes.MintAuth calldata auth, uint256 totalAmount) private {
        emit SettlementRecorded(
            auth.messageId,
            auth.isoType,
            auth.isoHash,
            auth.accountingRef,
            uint8(auth.fundsStatus),
            auth.corridor,
            uint8(auth.assetClass),
            totalAmount
        );
        for (uint256 i = 0; i < auth.recipients.length; i++) {
            emit MintExecuted(auth.messageId, auth.recipients[i], auth.amounts[i], uint8(auth.assetClass));
        }
        emit MessageIdConsumed(auth.messageId);
    }

}
