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

import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

/**
 * @title CWRedemptionQueue
 * @notice Tiered redemption for hub c* collateral released from cW burn flows.
 * @dev Instant <= instantTierMax, standard <= standardTierMax (24h delay), above => extended queue.
 */
contract CWRedemptionQueue is AccessControl, ReentrancyGuard {
    using SafeERC20 for IERC20;

    bytes32 public constant BRIDGE_ROLE = keccak256("BRIDGE_ROLE");
    bytes32 public constant PROCESSOR_ROLE = keccak256("PROCESSOR_ROLE");

    enum Tier {
        Instant,
        Standard,
        Extended
    }

    enum Status {
        Pending,
        Ready,
        Claimed,
        Cancelled
    }

    struct Request {
        address beneficiary;
        address token;
        uint256 amount;
        Tier tier;
        Status status;
        uint64 readyAt;
    }

    uint256 public instantTierMax = 5_000 * 1e6;
    uint256 public standardTierMax = 50_000 * 1e6;
    uint256 public standardDelay = 24 hours;
    uint256 public extendedDelay = 72 hours;

    uint256 public nextRequestId = 1;
    mapping(uint256 => Request) public requests;

    event TierLimitsUpdated(uint256 instantMax, uint256 standardMax, uint256 standardDelaySec, uint256 extendedDelaySec);
    event RedemptionEnqueued(uint256 indexed requestId, address indexed beneficiary, address indexed token, uint256 amount, Tier tier);
    event RedemptionReady(uint256 indexed requestId);
    event RedemptionClaimed(uint256 indexed requestId, address indexed beneficiary, uint256 amount);

    error ZeroAddress();
    error InvalidAmount();
    error NotReady();
    error InvalidStatus();

    constructor(address admin) {
        if (admin == address(0)) {
            revert ZeroAddress();
        }
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(PROCESSOR_ROLE, admin);
    }

    function setTierLimits(
        uint256 instantMax,
        uint256 standardMax,
        uint256 standardDelaySec,
        uint256 extendedDelaySec
    ) external onlyRole(DEFAULT_ADMIN_ROLE) {
        instantTierMax = instantMax;
        standardTierMax = standardMax;
        standardDelay = standardDelaySec;
        extendedDelay = extendedDelaySec;
        emit TierLimitsUpdated(instantMax, standardMax, standardDelaySec, extendedDelaySec);
    }

    function enqueueRedemption(
        address beneficiary,
        address token,
        uint256 amount
    ) external onlyRole(BRIDGE_ROLE) nonReentrant returns (uint256 requestId) {
        if (beneficiary == address(0) || token == address(0) || amount == 0) {
            revert InvalidAmount();
        }

        Tier tier = _tierForAmount(amount);
        uint64 readyAt = tier == Tier.Instant ? uint64(block.timestamp) : uint64(block.timestamp + _delayForTier(tier));
        Status status = tier == Tier.Instant ? Status.Ready : Status.Pending;

        requestId = nextRequestId++;
        requests[requestId] = Request({
            beneficiary: beneficiary,
            token: token,
            amount: amount,
            tier: tier,
            status: status,
            readyAt: readyAt
        });

        emit RedemptionEnqueued(requestId, beneficiary, token, amount, tier);
    }

    function fundAndEnqueue(
        address beneficiary,
        address token,
        uint256 amount
    ) external nonReentrant returns (uint256 requestId) {
        if (beneficiary == address(0) || token == address(0) || amount == 0) {
            revert InvalidAmount();
        }
        IERC20(token).safeTransferFrom(msg.sender, address(this), amount);

        Tier tier = _tierForAmount(amount);
        uint64 readyAt = tier == Tier.Instant ? uint64(block.timestamp) : uint64(block.timestamp + _delayForTier(tier));
        Status status = tier == Tier.Instant ? Status.Ready : Status.Pending;

        requestId = nextRequestId++;
        requests[requestId] = Request({
            beneficiary: beneficiary,
            token: token,
            amount: amount,
            tier: tier,
            status: status,
            readyAt: readyAt
        });

        emit RedemptionEnqueued(requestId, beneficiary, token, amount, tier);
    }

    function markReady(uint256 requestId) external onlyRole(PROCESSOR_ROLE) {
        Request storage req = requests[requestId];
        if (req.status != Status.Pending) {
            revert InvalidStatus();
        }
        req.status = Status.Ready;
        emit RedemptionReady(requestId);
    }

    function claim(uint256 requestId) external nonReentrant {
        Request storage req = requests[requestId];
        if (req.status != Status.Ready) {
            revert InvalidStatus();
        }
        if (block.timestamp < req.readyAt) {
            revert NotReady();
        }
        if (msg.sender != req.beneficiary && !hasRole(PROCESSOR_ROLE, msg.sender)) {
            revert("CWRedemptionQueue: not beneficiary");
        }

        req.status = Status.Claimed;
        IERC20(req.token).safeTransfer(req.beneficiary, req.amount);
        emit RedemptionClaimed(requestId, req.beneficiary, req.amount);
    }

    function processDueRequests(uint256[] calldata requestIds) external onlyRole(PROCESSOR_ROLE) {
        for (uint256 i = 0; i < requestIds.length; i++) {
            Request storage req = requests[requestIds[i]];
            if (req.status == Status.Pending && block.timestamp >= req.readyAt) {
                req.status = Status.Ready;
                emit RedemptionReady(requestIds[i]);
            }
        }
    }

    function _tierForAmount(uint256 amount) internal view returns (Tier) {
        if (amount <= instantTierMax) {
            return Tier.Instant;
        }
        if (amount <= standardTierMax) {
            return Tier.Standard;
        }
        return Tier.Extended;
    }

    function _delayForTier(Tier tier) internal view returns (uint256) {
        if (tier == Tier.Standard) {
            return standardDelay;
        }
        if (tier == Tier.Extended) {
            return extendedDelay;
        }
        return 0;
    }
}
