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

import "@openzeppelin/contracts/access/AccessControl.sol";

contract DBIS_SignerRegistry is AccessControl {
    bytes32 public constant SIGNER_ADMIN_ROLE = keccak256("SIGNER_ADMIN");

    uint8 public constant CATEGORY_OPS = 0;
    uint8 public constant CATEGORY_COMPLIANCE = 1;
    uint8 public constant CATEGORY_CUSTODY = 2;
    uint8 public constant CATEGORY_RISK = 3;
    uint8 public constant CATEGORY_AUDITOR = 4;

    uint256 private constant NEVER_REVOKED = type(uint256).max;

    struct SignerInfo {
        uint8 category;
        uint256 effectiveFromBlock;
        uint256 revokedAtBlock;
        bool exists;
    }

    mapping(address => SignerInfo) private _signers;
    address[] private _signerList;

    uint8 public requiredSignatures = 3;
    uint256 public categoryMaskRequired = 1 << CATEGORY_COMPLIANCE;
    uint256 public categoryMaskAllowed = (1 << CATEGORY_OPS) | (1 << CATEGORY_COMPLIANCE) | (1 << CATEGORY_CUSTODY) | (1 << CATEGORY_RISK) | (1 << CATEGORY_AUDITOR);

    event SignerAdded(address indexed signer, uint8 category);
    event SignerRemoved(address indexed signer);
    event QuorumUpdated(uint8 requiredSigs, uint256 requiredMask, uint256 allowedMask);

    constructor(address admin) {
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(SIGNER_ADMIN_ROLE, admin);
    }

    function addSigner(address signer, uint8 category) external onlyRole(SIGNER_ADMIN_ROLE) {
        require(signer != address(0), "DBIS: zero signer");
        require(!_signers[signer].exists, "DBIS: already signer");
        require(category <= CATEGORY_AUDITOR, "DBIS: invalid category");
        _signers[signer] = SignerInfo({
            category: category,
            effectiveFromBlock: block.number,
            revokedAtBlock: NEVER_REVOKED,
            exists: true
        });
        _signerList.push(signer);
        emit SignerAdded(signer, category);
    }

    function removeSigner(address signer) external onlyRole(SIGNER_ADMIN_ROLE) {
        require(_signers[signer].exists, "DBIS: not signer");
        if (_signers[signer].revokedAtBlock == NEVER_REVOKED) {
            _signers[signer].revokedAtBlock = block.number;
        }
        _signers[signer].exists = false;
        for (uint256 i = 0; i < _signerList.length; i++) {
            if (_signerList[i] == signer) {
                _signerList[i] = _signerList[_signerList.length - 1];
                _signerList.pop();
                break;
            }
        }
        emit SignerRemoved(signer);
    }

    function revokeSignerAtBlock(address signer) external onlyRole(SIGNER_ADMIN_ROLE) {
        require(_signers[signer].exists, "DBIS: not signer");
        _signers[signer].revokedAtBlock = block.number;
    }

    function setQuorum(uint8 requiredSigs, uint256 requiredMask, uint256 allowedMask) external onlyRole(SIGNER_ADMIN_ROLE) {
        requiredSignatures = requiredSigs;
        categoryMaskRequired = requiredMask;
        categoryMaskAllowed = allowedMask;
        emit QuorumUpdated(requiredSigs, requiredMask, allowedMask);
    }

    function isSigner(address signer) external view returns (bool) {
        return _signers[signer].exists && _signers[signer].revokedAtBlock == NEVER_REVOKED;
    }

    function isSignerActiveAtBlock(address signer, uint256 blockNum) external view returns (bool) {
        SignerInfo memory info = _signers[signer];
        if (!info.exists) return false;
        if (blockNum < info.effectiveFromBlock) return false;
        if (info.revokedAtBlock != NEVER_REVOKED && blockNum >= info.revokedAtBlock) return false;
        return true;
    }

    function areSignersActiveAtBlock(address[] calldata signers, uint256 blockNum) external view returns (bool) {
        for (uint256 i = 0; i < signers.length; i++) {
            if (!this.isSignerActiveAtBlock(signers[i], blockNum)) return false;
        }
        return true;
    }

    function hasDuplicateSigners(address[] calldata signers) external pure returns (bool) {
        for (uint256 i = 0; i < signers.length; i++) {
            for (uint256 j = i + 1; j < signers.length; j++) {
                if (signers[i] == signers[j]) return true;
            }
        }
        return false;
    }

    function getSignerInfo(address signer) external view returns (uint8 category, uint256 effectiveFromBlock, uint256 revokedAtBlock) {
        SignerInfo memory info = _signers[signer];
        require(info.exists, "DBIS: not signer");
        return (info.category, info.effectiveFromBlock, info.revokedAtBlock);
    }

    function validateSigners(address[] calldata signers) external view returns (bool ok, string memory reason) {
        if (signers.length < requiredSignatures) return (false, "insufficient count");
        uint256 categoryMask = 0;
        for (uint256 i = 0; i < signers.length; i++) {
            address s = signers[i];
            (bool exists, uint8 cat, uint256 revoked) = _getSignerInfo(s);
            if (!exists) return (false, "not signer");
            if (revoked != NEVER_REVOKED) return (false, "signer revoked");
            if ((categoryMaskAllowed & (1 << cat)) == 0) return (false, "category not allowed");
            if (_hasDuplicate(signers, i, s)) return (false, "duplicate signer");
            categoryMask |= (1 << cat);
        }
        if ((categoryMask & categoryMaskRequired) != categoryMaskRequired) return (false, "required category missing");
        return (true, "");
    }

    function _getSignerInfo(address signer) private view returns (bool exists, uint8 category, uint256 revokedAtBlock) {
        SignerInfo memory info = _signers[signer];
        return (info.exists, info.category, info.revokedAtBlock);
    }

    function _hasDuplicate(address[] calldata signers, uint256 currentIndex, address signer) private pure returns (bool) {
        for (uint256 j = currentIndex + 1; j < signers.length; j++) {
            if (signers[j] == signer) return true;
        }
        return false;
    }

    function getSignerCount() external view returns (uint256) {
        return _signerList.length;
    }

    uint256 public largeSwapAmountThreshold;
    uint8 public requiredSignaturesSmall = 2;
    uint8 public requiredSignaturesLarge = 3;

    function setSwapQuorum(uint256 largeThreshold, uint8 smallSigs, uint8 largeSigs) external onlyRole(SIGNER_ADMIN_ROLE) {
        largeSwapAmountThreshold = largeThreshold;
        requiredSignaturesSmall = smallSigs;
        requiredSignaturesLarge = largeSigs;
    }

    function validateSignersForSwap(address[] calldata signers, uint256 amountIn) external view returns (bool ok, string memory reason) {
        uint8 required = amountIn > largeSwapAmountThreshold ? requiredSignaturesLarge : requiredSignaturesSmall;
        if (signers.length < required) return (false, "insufficient count");
        uint256 categoryMask = 0;
        for (uint256 i = 0; i < signers.length; i++) {
            address s = signers[i];
            (bool exists, uint8 cat, uint256 revoked) = _getSignerInfo(s);
            if (!exists) return (false, "not signer");
            if (revoked != NEVER_REVOKED) return (false, "signer revoked");
            if ((categoryMaskAllowed & (1 << cat)) == 0) return (false, "category not allowed");
            if (_hasDuplicate(signers, i, s)) return (false, "duplicate signer");
            categoryMask |= (1 << cat);
        }
        if (amountIn > largeSwapAmountThreshold && (categoryMask & categoryMaskRequired) != categoryMaskRequired) {
            return (false, "required category missing");
        }
        return (true, "");
    }
}
