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

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {IERC3156FlashBorrower} from "@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol";
import {IERC3156FlashLender} from "@openzeppelin/contracts/interfaces/IERC3156FlashLender.sol";
import "./IRWAToken.sol";

/**
 * @title LiIndexFlashVault
 * @notice IERC3156 flash lender for M00 Li* index tokens held in OMNL institutional vault.
 * @dev Flash borrow permitted only when utilization LTV is below GRU Monetary Policy ceiling
 *      (default 80% = 20% operational buffer). Enforces index freshness and MPAP parity band.
 */
contract LiIndexFlashVault is IERC3156FlashLender, AccessControl, ReentrancyGuard {
    using SafeERC20 for IERC20;

    bytes32 public constant VAULT_MANAGER_ROLE = keccak256("VAULT_MANAGER_ROLE");
    bytes32 private constant _FLASH_RETURN = keccak256("ERC3156FlashBorrower.onFlashLoan");

    /// @notice Max utilization LTV before flash disabled (8000 = 80%, retains 20% buffer).
    uint256 public maxUtilizationLtvBps;
    /// @notice Max single flash as share of vault balance (5000 = 50%).
    uint256 public maxFlashShareOfVaultBps;
    /// @notice Flash fee in basis points (≤ 9 bps per policy).
    uint256 public flashFeeBps;
    /// @notice MPAP parity band — block flash if index drift vs XAU anchor exceeds this.
    uint256 public mpapParityBandBps;
    /// @notice Max indexValue age (seconds) for flash eligibility.
    uint256 public maxIndexAgeSeconds;
    /// @notice XAU/USD anchor for parity drift check (8 decimals, e.g. 2400_00000000).
    uint256 public xauUsdAnchor8;

    mapping(address => bool) public supportedToken;
    mapping(address => uint256) public vaultBalance;
    mapping(address => uint256) public outstandingFlash;

    event TokenSupported(address indexed token, bool supported);
    event Deposited(address indexed token, address indexed from, uint256 amount);
    event Withdrawn(address indexed token, address indexed to, uint256 amount);
    event RiskParamsUpdated(
        uint256 maxUtilizationLtvBps,
        uint256 maxFlashShareOfVaultBps,
        uint256 flashFeeBps,
        uint256 mpapParityBandBps
    );

    constructor(
        address admin,
        uint256 maxUtilizationLtvBps_,
        uint256 maxFlashShareOfVaultBps_,
        uint256 flashFeeBps_,
        uint256 mpapParityBandBps_,
        uint256 maxIndexAgeSeconds_,
        uint256 xauUsdAnchor8_
    ) {
        require(admin != address(0), "LiIndexFlashVault: zero admin");
        require(maxUtilizationLtvBps_ <= 10_000, "LiIndexFlashVault: ltv");
        require(maxFlashShareOfVaultBps_ <= 10_000, "LiIndexFlashVault: flash share");
        require(flashFeeBps_ <= 1000, "LiIndexFlashVault: fee");

        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(VAULT_MANAGER_ROLE, admin);

        maxUtilizationLtvBps = maxUtilizationLtvBps_;
        maxFlashShareOfVaultBps = maxFlashShareOfVaultBps_;
        flashFeeBps = flashFeeBps_;
        mpapParityBandBps = mpapParityBandBps_;
        maxIndexAgeSeconds = maxIndexAgeSeconds_;
        xauUsdAnchor8 = xauUsdAnchor8_;
    }

    function setSupportedToken(address token, bool supported) external onlyRole(VAULT_MANAGER_ROLE) {
        supportedToken[token] = supported;
        emit TokenSupported(token, supported);
    }

    function setRiskParams(
        uint256 maxUtilizationLtvBps_,
        uint256 maxFlashShareOfVaultBps_,
        uint256 flashFeeBps_,
        uint256 mpapParityBandBps_
    ) external onlyRole(DEFAULT_ADMIN_ROLE) {
        require(maxUtilizationLtvBps_ <= 10_000, "LiIndexFlashVault: ltv");
        require(maxFlashShareOfVaultBps_ <= 10_000, "LiIndexFlashVault: flash share");
        require(flashFeeBps_ <= 1000, "LiIndexFlashVault: fee");
        maxUtilizationLtvBps = maxUtilizationLtvBps_;
        maxFlashShareOfVaultBps = maxFlashShareOfVaultBps_;
        flashFeeBps = flashFeeBps_;
        mpapParityBandBps = mpapParityBandBps_;
        emit RiskParamsUpdated(maxUtilizationLtvBps_, maxFlashShareOfVaultBps_, flashFeeBps_, mpapParityBandBps_);
    }

    function deposit(address token, uint256 amount) external nonReentrant {
        require(supportedToken[token], "LiIndexFlashVault: unsupported");
        IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
        vaultBalance[token] += amount;
        emit Deposited(token, msg.sender, amount);
    }

    function withdraw(address token, uint256 amount, address to) external nonReentrant onlyRole(VAULT_MANAGER_ROLE) {
        require(to != address(0), "LiIndexFlashVault: zero to");
        vaultBalance[token] -= amount;
        IERC20(token).safeTransfer(to, amount);
        emit Withdrawn(token, to, amount);
    }

    function currentUtilizationBps(address token) public view returns (uint256) {
        uint256 bal = vaultBalance[token];
        if (bal == 0) return 0;
        return (outstandingFlash[token] * 10_000) / bal;
    }

    function flashBorrowEligible(address token) public view returns (bool) {
        if (!supportedToken[token]) return false;
        if (currentUtilizationBps(token) >= maxUtilizationLtvBps) return false;

        IRWAToken idx = IRWAToken(token);
        if (!idx.isRwaIndex()) return false;

        uint256 updatedAt = idx.indexUpdatedAt();
        if (updatedAt == 0 || block.timestamp - updatedAt > maxIndexAgeSeconds) return false;

        uint256 indexLevel = idx.indexValue();
        if (indexLevel == 0) return false;

        if (xauUsdAnchor8 > 0 && mpapParityBandBps > 0) {
            uint256 anchorLevel6 = (xauUsdAnchor8 / 1e2);
            if (anchorLevel6 > 0) {
                uint256 driftBps = indexLevel > anchorLevel6
                    ? ((indexLevel - anchorLevel6) * 10_000) / indexLevel
                    : ((anchorLevel6 - indexLevel) * 10_000) / anchorLevel6;
                if (driftBps >= mpapParityBandBps) return false;
            }
        }
        return true;
    }

    function maxFlashLoan(address token) external view returns (uint256) {
        if (!flashBorrowEligible(token)) return 0;
        uint256 bal = vaultBalance[token];
        uint256 outstanding = outstandingFlash[token];
        uint256 lendable = (bal * maxUtilizationLtvBps) / 10_000;
        if (outstanding >= lendable) return 0;
        uint256 headroom = lendable - outstanding;
        uint256 cap = (bal * maxFlashShareOfVaultBps) / 10_000;
        return headroom < cap ? headroom : cap;
    }

    function flashFee(address token, uint256 amount) external view returns (uint256) {
        token;
        amount;
        return (amount * flashFeeBps) / 10_000;
    }

    function flashLoan(
        IERC3156FlashBorrower receiver,
        address token,
        uint256 amount,
        bytes calldata data
    ) external nonReentrant returns (bool) {
        require(flashBorrowEligible(token), "LiIndexFlashVault: not eligible");
        require(amount <= this.maxFlashLoan(token), "LiIndexFlashVault: exceeds max");

        uint256 fee = (amount * flashFeeBps) / 10_000;
        outstandingFlash[token] += amount;
        IERC20(token).safeTransfer(address(receiver), amount);

        require(
            receiver.onFlashLoan(msg.sender, token, amount, fee, data) == _FLASH_RETURN,
            "LiIndexFlashVault: callback failed"
        );

        IERC20(token).safeTransferFrom(address(receiver), address(this), amount + fee);
        outstandingFlash[token] -= amount;
        if (fee > 0) {
            vaultBalance[token] += fee;
        }
        return true;
    }
}
