// 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 "./IReserveSystem.sol";

/**
 * @title ReserveSystem
 * @notice Core implementation of the GRU Reserve System
 * @dev Manages reserves in multiple asset classes (XAU, digital assets, sovereign instruments)
 * Implements XAU triangulation conversion algorithm and redemption mechanisms
 */
contract ReserveSystem is IReserveSystem, AccessControl, ReentrancyGuard {
    using SafeERC20 for IERC20;

    // ============ Constants ============

    bytes32 public constant RESERVE_MANAGER_ROLE = keccak256("RESERVE_MANAGER_ROLE");
    bytes32 public constant PRICE_FEED_ROLE = keccak256("PRICE_FEED_ROLE");
    bytes32 public constant CONVERSION_OPERATOR_ROLE = keccak256("CONVERSION_OPERATOR_ROLE");

    // Conversion fee parameters (basis points: 10000 = 100%)
    uint256 public constant BASE_FEE_BPS = 10; // 0.1%
    uint256 public constant SLIPPAGE_FEE_BPS = 50; // 0.5% per 1% slippage
    uint256 public constant LARGE_TRANSACTION_THRESHOLD = 1_000_000 * 1e18; // $1M
    uint256 public constant LARGE_TRANSACTION_FEE_BPS = 5; // 0.05%

    // Price staleness threshold (30 seconds)
    uint256 public constant PRICE_STALENESS_THRESHOLD = 30;

    // Maximum slippage (0.5% for liquid, 1.0% for less liquid)
    uint256 public constant MAX_SLIPPAGE_BPS = 50; // 0.5%

    // ============ Storage ============

    struct Reserve {
        address asset;
        uint256 balance;
        uint256 lastUpdated;
    }

    struct PriceFeed {
        uint256 price;
        uint256 timestamp;
        bool isValid;
    }

    struct Conversion {
        address sourceAsset;
        address targetAsset;
        uint256 sourceAmount;
        uint256 targetAmount;
        uint256 fees;
        address[] path;
        uint256 timestamp;
    }

    // Reserve tracking
    mapping(address => uint256) public reserveBalances;
    mapping(bytes32 => Reserve) public reserves;
    bytes32[] public reserveIds;

    // Price feeds
    mapping(address => PriceFeed) public priceFeeds;
    address[] public supportedAssets;

    // Conversion tracking
    mapping(bytes32 => Conversion) public conversions;
    bytes32[] public conversionIds;

    // Asset metadata
    mapping(address => bool) public isSupportedAsset;
    mapping(address => bool) public isLiquidAsset; // For slippage calculation

    // ============ Constructor ============

    constructor(address admin) {
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(RESERVE_MANAGER_ROLE, admin);
        _grantRole(PRICE_FEED_ROLE, admin);
        _grantRole(CONVERSION_OPERATOR_ROLE, admin);
    }

    // ============ Reserve Management ============

    function depositReserve(
        address asset,
        uint256 amount
    ) external override onlyRole(RESERVE_MANAGER_ROLE) nonReentrant returns (bytes32 reserveId) {
        require(asset != address(0), "ReserveSystem: zero address");
        require(amount > 0, "ReserveSystem: zero amount");
        require(isSupportedAsset[asset], "ReserveSystem: unsupported asset");

        IERC20(asset).safeTransferFrom(msg.sender, address(this), amount);

        reserveId = keccak256(abi.encodePacked(asset, amount, block.timestamp, reserveIds.length));
        reserves[reserveId] = Reserve({
            asset: asset,
            balance: amount,
            lastUpdated: block.timestamp
        });
        reserveIds.push(reserveId);
        reserveBalances[asset] += amount;

        emit ReserveDeposited(asset, amount, msg.sender, reserveId);
    }

    function withdrawReserve(
        address asset,
        uint256 amount,
        address recipient
    ) external override onlyRole(RESERVE_MANAGER_ROLE) nonReentrant returns (bytes32 reserveId) {
        require(asset != address(0), "ReserveSystem: zero address");
        require(recipient != address(0), "ReserveSystem: zero recipient");
        require(amount > 0, "ReserveSystem: zero amount");
        require(reserveBalances[asset] >= amount, "ReserveSystem: insufficient reserve");

        reserveBalances[asset] -= amount;

        IERC20(asset).safeTransfer(recipient, amount);

        reserveId = keccak256(abi.encodePacked(asset, amount, block.timestamp, reserveIds.length));

        emit ReserveWithdrawn(asset, amount, recipient, reserveId);
    }

    function getReserveBalance(address asset) external view override returns (uint256) {
        return reserveBalances[asset];
    }

    function getReserveById(bytes32 reserveId) external view override returns (address asset, uint256 balance) {
        Reserve memory reserve = reserves[reserveId];
        return (reserve.asset, reserve.balance);
    }

    // ============ Conversion ============

    function convertAssets(
        address sourceAsset,
        address targetAsset,
        uint256 amount
    ) external override onlyRole(CONVERSION_OPERATOR_ROLE) nonReentrant returns (
        bytes32 conversionId,
        uint256 targetAmount,
        uint256 fees
    ) {
        require(sourceAsset != address(0), "ReserveSystem: zero source asset");
        require(targetAsset != address(0), "ReserveSystem: zero target asset");
        require(amount > 0, "ReserveSystem: zero amount");
        require(isSupportedAsset[sourceAsset], "ReserveSystem: unsupported source asset");
        require(isSupportedAsset[targetAsset], "ReserveSystem: unsupported target asset");

        // Calculate conversion
        (uint256 calculatedAmount, uint256 calculatedFees, address[] memory path) =
            calculateConversion(sourceAsset, targetAsset, amount);

        require(reserveBalances[targetAsset] >= calculatedAmount, "ReserveSystem: insufficient target reserve");

        // Transfer source asset
        IERC20(sourceAsset).safeTransferFrom(msg.sender, address(this), amount);
        reserveBalances[sourceAsset] += amount;

        // Transfer target asset
        reserveBalances[targetAsset] -= calculatedAmount;
        IERC20(targetAsset).safeTransfer(msg.sender, calculatedAmount);

        conversionId = keccak256(abi.encodePacked(sourceAsset, targetAsset, amount, block.timestamp, conversionIds.length));
        conversions[conversionId] = Conversion({
            sourceAsset: sourceAsset,
            targetAsset: targetAsset,
            sourceAmount: amount,
            targetAmount: calculatedAmount,
            fees: calculatedFees,
            path: path,
            timestamp: block.timestamp
        });
        conversionIds.push(conversionId);

        emit ConversionExecuted(sourceAsset, targetAsset, amount, calculatedAmount, conversionId, calculatedFees);

        return (conversionId, calculatedAmount, calculatedFees);
    }

    function calculateConversion(
        address sourceAsset,
        address targetAsset,
        uint256 amount
    ) public view override returns (
        uint256 targetAmount,
        uint256 fees,
        address[] memory path
    ) {
        require(sourceAsset != address(0), "ReserveSystem: zero source asset");
        require(targetAsset != address(0), "ReserveSystem: zero target asset");
        require(amount > 0, "ReserveSystem: zero amount");

        // Get prices
        (uint256 sourcePrice, uint256 sourceTimestamp) = getPrice(sourceAsset);
        (uint256 targetPrice, uint256 targetTimestamp) = getPrice(targetAsset);

        require(sourceTimestamp > 0 && targetTimestamp > 0, "ReserveSystem: price feed not available");
        require(block.timestamp - sourceTimestamp <= PRICE_STALENESS_THRESHOLD, "ReserveSystem: stale source price");
        require(block.timestamp - targetTimestamp <= PRICE_STALENESS_THRESHOLD, "ReserveSystem: stale target price");

        // targetAmount in targetAsset units (same value in USD terms)
        targetAmount = (amount * sourcePrice) / targetPrice;

        // Calculate fees
        fees = calculateFees(targetAmount, 0); // No slippage for direct conversion

        // Simple path (direct conversion)
        path = new address[](2);
        path[0] = sourceAsset;
        path[1] = targetAsset;

        return (targetAmount, fees, path);
    }

    function calculateFees(uint256 amount, uint256 slippageBps) internal pure returns (uint256) {
        uint256 baseFee = (amount * BASE_FEE_BPS) / 10000;
        uint256 slippageFee = 0;

        if (slippageBps > 10) { // 0.1% threshold
            slippageFee = (amount * slippageBps * SLIPPAGE_FEE_BPS) / 1000000;
        }

        uint256 largeTransactionFee = 0;
        if (amount >= LARGE_TRANSACTION_THRESHOLD) {
            largeTransactionFee = (amount * LARGE_TRANSACTION_FEE_BPS) / 10000;
        }

        return baseFee + slippageFee + largeTransactionFee;
    }

    // ============ Redemption ============

    function redeem(
        address asset,
        uint256 amount,
        address recipient
    ) external override onlyRole(RESERVE_MANAGER_ROLE) nonReentrant returns (bytes32 redemptionId) {
        require(asset != address(0), "ReserveSystem: zero address");
        require(recipient != address(0), "ReserveSystem: zero recipient");
        require(amount > 0, "ReserveSystem: zero amount");
        require(reserveBalances[asset] >= amount, "ReserveSystem: insufficient reserve");

        reserveBalances[asset] -= amount;
        IERC20(asset).safeTransfer(recipient, amount);

        redemptionId = keccak256(abi.encodePacked(asset, amount, block.timestamp, conversionIds.length));

        emit RedemptionExecuted(asset, amount, recipient, redemptionId);
    }

    // ============ Price Feeds ============

    function updatePriceFeed(
        address asset,
        uint256 price,
        uint256 timestamp
    ) external override onlyRole(PRICE_FEED_ROLE) {
        require(asset != address(0), "ReserveSystem: zero address");
        require(price > 0, "ReserveSystem: zero price");
        require(timestamp <= block.timestamp, "ReserveSystem: future timestamp");

        if (!isSupportedAsset[asset]) {
            isSupportedAsset[asset] = true;
            supportedAssets.push(asset);
        }

        priceFeeds[asset] = PriceFeed({
            price: price,
            timestamp: timestamp,
            isValid: true
        });

        emit PriceFeedUpdated(asset, price, timestamp);
    }

    function getPrice(address asset) public view override returns (uint256 price, uint256 timestamp) {
        PriceFeed memory feed = priceFeeds[asset];
        require(feed.isValid, "ReserveSystem: price feed not available");
        return (feed.price, feed.timestamp);
    }

    function getConversionPrice(
        address sourceAsset,
        address targetAsset
    ) external view override returns (uint256) {
        (uint256 sourcePrice,) = getPrice(sourceAsset);
        (uint256 targetPrice,) = getPrice(targetAsset);
        // target per source (e.g. USDC per WETH when source=WETH, target=USDC)
        return (sourcePrice * 1e18) / targetPrice;
    }

    // ============ Admin Functions ============

    function addSupportedAsset(address asset, bool isLiquid) external onlyRole(DEFAULT_ADMIN_ROLE) {
        require(asset != address(0), "ReserveSystem: zero address");
        isSupportedAsset[asset] = true;
        isLiquidAsset[asset] = isLiquid;
        if (!_isInArray(asset, supportedAssets)) {
            supportedAssets.push(asset);
        }
    }

    function removeSupportedAsset(address asset) external onlyRole(DEFAULT_ADMIN_ROLE) {
        isSupportedAsset[asset] = false;
    }

    function _isInArray(address item, address[] memory array) internal pure returns (bool) {
        for (uint256 i = 0; i < array.length; i++) {
            if (array[i] == item) {
                return true;
            }
        }
        return false;
    }

    function getSupportedAssets() external view returns (address[] memory) {
        return supportedAssets;
    }
}

