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

import "@openzeppelin/contracts/access/AccessControl.sol";
import "../compliance/libraries/MonetaryFormulas.sol";
import "./IGruMonetaryPolicyGate.sol";

/**
 * @title GruMonetaryPolicyGate
 * @notice Enforces GRU Monetary Policy guardrails before Li* / M00 mint.
 * @dev Operator updates metrics from `run-gru-monetary-metrics.mjs`; RWAToken mint calls canMint.
 */
contract GruMonetaryPolicyGate is IGruMonetaryPolicyGate, AccessControl {
    bytes32 public constant METRICS_PUBLISHER_ROLE = keccak256("METRICS_PUBLISHER_ROLE");
    bytes32 public constant GATE_ADMIN_ROLE = keccak256("GATE_ADMIN_ROLE");

    uint256 public immutable coverageMinBps;
    uint256 public immutable coverageAlertBps;
    uint256 public immutable maxM1ToM00Utilization;

    MetricsSnapshot private _metrics;
    mapping(address => bool) public override tokenGateEnabled;

    constructor(
        address admin,
        uint256 coverageMinBps_,
        uint256 coverageAlertBps_,
        uint256 maxM1ToM00Utilization_
    ) {
        require(admin != address(0), "GruMonetaryPolicyGate: admin");
        require(coverageMinBps_ >= coverageAlertBps_, "GruMonetaryPolicyGate: coverage");
        require(maxM1ToM00Utilization_ > 0, "GruMonetaryPolicyGate: util");

        coverageMinBps = coverageMinBps_;
        coverageAlertBps = coverageAlertBps_;
        maxM1ToM00Utilization = maxM1ToM00Utilization_;

        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(GATE_ADMIN_ROLE, admin);
        _grantRole(METRICS_PUBLISHER_ROLE, admin);
    }

    function metrics() external view override returns (MetricsSnapshot memory) {
        return _metrics;
    }

    function setTokenGate(address token, bool enabled) external onlyRole(GATE_ADMIN_ROLE) {
        tokenGateEnabled[token] = enabled;
        emit PolicyGateSet(token, enabled);
    }

    function updateMetrics(
        uint256 coverageRatioBps,
        uint256 m1ToM00Utilization,
        VelocityZone zone,
        bool issuancePaused
    ) external onlyRole(METRICS_PUBLISHER_ROLE) {
        _metrics = MetricsSnapshot({
            coverageRatioBps: coverageRatioBps,
            m1ToM00Utilization: m1ToM00Utilization,
            velocityZone: zone,
            metricsUpdatedAt: block.timestamp,
            issuancePaused: issuancePaused
        });
        emit MetricsUpdated(coverageRatioBps, m1ToM00Utilization, zone, issuancePaused);
    }

    function canMint(address token, uint256) external view override returns (bool) {
        (bool ok,) = canMintReason(token, 0);
        return ok;
    }

    function canMintReason(address token, uint256)
        public
        view
        override
        returns (bool ok, bytes32 reason)
    {
        if (!tokenGateEnabled[token]) {
            return (true, bytes32(0));
        }
        if (_metrics.issuancePaused) {
            return (false, keccak256("ISSUANCE_PAUSED"));
        }
        if (_metrics.velocityZone == VelocityZone.Black || _metrics.velocityZone == VelocityZone.Red) {
            return (false, keccak256("VELOCITY_ZONE_BLOCK"));
        }
        if (_metrics.coverageRatioBps < coverageAlertBps) {
            return (false, keccak256("COVERAGE_BELOW_ALERT"));
        }
        if (_metrics.m1ToM00Utilization > maxM1ToM00Utilization) {
            return (false, keccak256("M1_M00_UTILIZATION_EXCEEDED"));
        }
        if (_metrics.coverageRatioBps < coverageMinBps && _metrics.velocityZone >= VelocityZone.AmberHigh) {
            return (false, keccak256("COVERAGE_VELOCITY_COMBO"));
        }
        token;
        return (true, bytes32(0));
    }

    /// @notice Pure helper mirroring off-chain metrics job (coverage bps).
    function computeCoverageBps(uint256 reserveValue, uint256 circulatingValue) external pure returns (uint256) {
        return MonetaryFormulas.coverageRatioBps(reserveValue, circulatingValue);
    }
}
