// 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";
import "../emoney/interfaces/IeMoneyToken.sol";
import "../emoney/interfaces/ITokenFactory138.sol";
import "./IReserveSystem.sol";

/**
 * @title ReserveTokenIntegration
 * @notice Integrates Reserve System with eMoney Token Factory
 * @dev Enables eMoney tokens to be backed by reserves and converted via the reserve system
 */
contract ReserveTokenIntegration is AccessControl, ReentrancyGuard {
    using SafeERC20 for IERC20;

    bytes32 public constant INTEGRATION_OPERATOR_ROLE = keccak256("INTEGRATION_OPERATOR_ROLE");

    IReserveSystem public reserveSystem;
    ITokenFactory138 public tokenFactory;

    // Token to reserve asset mapping
    mapping(address => address) public tokenReserveAsset;
    // Reserve asset to token mapping
    mapping(address => address) public reserveAssetToken;

    // Reserve backing ratio (basis points: 10000 = 100%)
    mapping(address => uint256) public reserveBackingRatio;

    event TokenBackedByReserve(
        address indexed token,
        address indexed reserveAsset,
        uint256 backingRatio
    );

    event ReserveBackingUpdated(
        address indexed token,
        address indexed reserveAsset,
        uint256 newBackingRatio
    );

    event TokenConvertedViaReserve(
        address indexed sourceToken,
        address indexed targetToken,
        uint256 sourceAmount,
        uint256 targetAmount,
        bytes32 conversionId
    );

    constructor(
        address admin,
        address reserveSystem_,
        address tokenFactory_
    ) {
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(INTEGRATION_OPERATOR_ROLE, admin);

        reserveSystem = IReserveSystem(reserveSystem_);
        tokenFactory = ITokenFactory138(tokenFactory_);
    }

    /**
     * @notice Set reserve asset backing for an eMoney token
     * @param token Address of the eMoney token
     * @param reserveAsset Address of the reserve asset
     * @param backingRatio Backing ratio in basis points (10000 = 100%)
     */
    function setTokenBacking(
        address token,
        address reserveAsset,
        uint256 backingRatio
    ) external onlyRole(INTEGRATION_OPERATOR_ROLE) {
        require(token != address(0), "ReserveTokenIntegration: zero token");
        require(reserveAsset != address(0), "ReserveTokenIntegration: zero reserve asset");
        require(backingRatio <= 10000, "ReserveTokenIntegration: invalid backing ratio");

        tokenReserveAsset[token] = reserveAsset;
        reserveAssetToken[reserveAsset] = token;
        reserveBackingRatio[token] = backingRatio;

        emit TokenBackedByReserve(token, reserveAsset, backingRatio);
    }

    /**
     * @notice Update reserve backing ratio for a token
     * @param token Address of the eMoney token
     * @param newBackingRatio New backing ratio in basis points
     */
    function updateBackingRatio(
        address token,
        uint256 newBackingRatio
    ) external onlyRole(INTEGRATION_OPERATOR_ROLE) {
        require(token != address(0), "ReserveTokenIntegration: zero token");
        require(newBackingRatio <= 10000, "ReserveTokenIntegration: invalid backing ratio");
        require(tokenReserveAsset[token] != address(0), "ReserveTokenIntegration: token not backed");

        address reserveAsset = tokenReserveAsset[token];
        reserveBackingRatio[token] = newBackingRatio;

        emit ReserveBackingUpdated(token, reserveAsset, newBackingRatio);
    }

    /**
     * @notice Convert eMoney tokens via reserve system
     * @param sourceToken Address of the source eMoney token
     * @param targetToken Address of the target eMoney token
     * @param amount Amount of source tokens to convert
     * @return targetAmount Amount of target tokens received
     * @return conversionId Conversion ID from reserve system
     */
    function convertTokensViaReserve(
        address sourceToken,
        address targetToken,
        uint256 amount
    ) external nonReentrant returns (uint256 targetAmount, bytes32 conversionId) {
        require(sourceToken != address(0), "ReserveTokenIntegration: zero source token");
        require(targetToken != address(0), "ReserveTokenIntegration: zero target token");
        require(amount > 0, "ReserveTokenIntegration: zero amount");

        address sourceReserveAsset = tokenReserveAsset[sourceToken];
        address targetReserveAsset = tokenReserveAsset[targetToken];

        require(sourceReserveAsset != address(0), "ReserveTokenIntegration: source not backed");
        require(targetReserveAsset != address(0), "ReserveTokenIntegration: target not backed");

        // Burn source tokens
        IeMoneyToken(sourceToken).burn(msg.sender, amount, "0x00");

        // Calculate reserve asset amount based on backing ratio
        uint256 sourceReserveAmount = (amount * reserveBackingRatio[sourceToken]) / 10000;

        // Convert via reserve system
        uint256 targetReserveAmount;
        uint256 fees;
        (conversionId, targetReserveAmount, fees) = reserveSystem.convertAssets(
            sourceReserveAsset,
            targetReserveAsset,
            sourceReserveAmount
        );

        // Calculate target token amount based on backing ratio
        targetAmount = (targetReserveAmount * 10000) / reserveBackingRatio[targetToken];

        // Mint target tokens
        IeMoneyToken(targetToken).mint(msg.sender, targetAmount, bytes32(0));

        emit TokenConvertedViaReserve(sourceToken, targetToken, amount, targetAmount, conversionId);

        return (targetAmount, conversionId);
    }

    /**
     * @notice Get reserve backing information for a token
     * @param token Address of the eMoney token
     * @return reserveAsset Address of the reserve asset
     * @return backingRatio Backing ratio in basis points
     * @return reserveBalance Current reserve balance
     */
    function getTokenBacking(address token) external view returns (
        address reserveAsset,
        uint256 backingRatio,
        uint256 reserveBalance
    ) {
        reserveAsset = tokenReserveAsset[token];
        backingRatio = reserveBackingRatio[token];
        if (reserveAsset != address(0)) {
            reserveBalance = reserveSystem.getReserveBalance(reserveAsset);
        }
    }

    /**
     * @notice Check if token has adequate reserve backing
     * @param token Address of the eMoney token
     * @return isAdequate True if reserves are adequate
     * @return requiredReserve Required reserve amount
     * @return currentReserve Current reserve amount
     */
    function checkReserveAdequacy(address token) external view returns (
        bool isAdequate,
        uint256 requiredReserve,
        uint256 currentReserve
    ) {
        address reserveAsset = tokenReserveAsset[token];
        require(reserveAsset != address(0), "ReserveTokenIntegration: token not backed");

        uint256 totalSupply = IERC20(token).totalSupply();
        uint256 backingRatio = reserveBackingRatio[token];

        requiredReserve = (totalSupply * backingRatio) / 10000;
        currentReserve = reserveSystem.getReserveBalance(reserveAsset);

        isAdequate = currentReserve >= requiredReserve;
    }
}

