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

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

/**
 * @title SimpleERC3156FlashVault
 * @notice Minimal ERC-3156 flash lender: whitelist per token, flat bps fee, same-block repayment invariant.
 * @dev Intended for Chain 138 / internal atomic workflows when no external flash source exists.
 */
contract SimpleERC3156FlashVault is IERC3156FlashLender, Ownable, ReentrancyGuard {
    using SafeERC20 for IERC20;

    bytes32 private constant _RETURN_VALUE = keccak256("ERC3156FlashBorrower.onFlashLoan");

    /// @notice Maximum owner-configurable fee (10%). Prevents griefing at 100% fee.
    uint256 public constant MAX_FEE_BPS = 1000;

    /// @notice Fee on borrowed amount, basis points (10_000 = 100%).
    uint256 public feeBps;

    mapping(address token => bool) public supportedToken;

    /// @notice When true, only `approvedBorrower[receiver]` may be used as the flash callback contract.
    bool public borrowerAllowlistEnabled;

    mapping(address borrower => bool) public approvedBorrower;

    /// @notice Cumulative fees retained by the vault per token (for ops / accounting).
    mapping(address token => uint256) public totalFeesCollected;

    /// @notice initiator = flashLoan caller; receiver = IERC3156FlashBorrower callback target.
    event FlashLoan(address indexed initiator, IERC3156FlashBorrower indexed receiver, address indexed token, uint256 amount, uint256 fee);
    event FeeBpsUpdated(uint256 feeBps);
    event TokenSupportUpdated(address indexed token, bool supported);
    event TokensRescued(address indexed token, address indexed to, uint256 amount);
    event BorrowerAllowlistEnabledUpdated(bool enabled);
    event BorrowerApprovalUpdated(address indexed borrower, bool approved);

    error UnsupportedToken();
    error ZeroAmount();
    error FeeTooHigh();
    error InvalidCallback();
    error RepaymentFailed();
    error ZeroRescue();
    error ZeroRecipient();
    error BorrowerNotApproved();

    constructor(address initialOwner, uint256 initialFeeBps) Ownable(initialOwner) {
        _setFeeBps(initialFeeBps);
    }

    function setFeeBps(uint256 newFeeBps) external onlyOwner {
        _setFeeBps(newFeeBps);
    }

    function setTokenSupported(address token, bool supported) external onlyOwner {
        supportedToken[token] = supported;
        emit TokenSupportUpdated(token, supported);
    }

    function setBorrowerAllowlistEnabled(bool enabled) external onlyOwner {
        borrowerAllowlistEnabled = enabled;
        emit BorrowerAllowlistEnabledUpdated(enabled);
    }

    function setBorrowerApproved(address borrower, bool approved) external onlyOwner {
        approvedBorrower[borrower] = approved;
        emit BorrowerApprovalUpdated(borrower, approved);
    }

    /// @notice Operator alias for `flashFee` (same revert rules).
    function previewFlashFee(address token, uint256 amount) external view returns (uint256) {
        return flashFee(token, amount);
    }

    /// @notice Owner-only recovery (mis-seeded asset, deprecated token, migration). Does not bypass flash invariants on active loans in the same tx.
    function rescueTokens(address token, uint256 amount, address to) external onlyOwner {
        if (amount == 0) revert ZeroRescue();
        if (to == address(0)) revert ZeroRecipient();
        IERC20(token).safeTransfer(to, amount);
        emit TokensRescued(token, to, amount);
    }

    function maxFlashLoan(address token) external view override returns (uint256) {
        if (!supportedToken[token]) {
            return 0;
        }
        return IERC20(token).balanceOf(address(this));
    }

    function flashFee(address token, uint256 amount) public view override returns (uint256) {
        if (!supportedToken[token]) {
            revert UnsupportedToken();
        }
        return (amount * feeBps) / 10_000;
    }

    function flashLoan(
        IERC3156FlashBorrower receiver,
        address token,
        uint256 amount,
        bytes calldata data
    ) external override nonReentrant returns (bool) {
        if (!supportedToken[token]) revert UnsupportedToken();
        if (amount == 0) revert ZeroAmount();
        if (borrowerAllowlistEnabled && !approvedBorrower[address(receiver)]) {
            revert BorrowerNotApproved();
        }

        uint256 fee = flashFee(token, amount);
        uint256 balanceBefore = IERC20(token).balanceOf(address(this));
        if (balanceBefore < amount) revert RepaymentFailed();

        IERC20(token).safeTransfer(address(receiver), amount);

        bytes32 retval = receiver.onFlashLoan(msg.sender, token, amount, fee, data);
        if (retval != _RETURN_VALUE) revert InvalidCallback();

        if (IERC20(token).balanceOf(address(this)) < balanceBefore + fee) {
            revert RepaymentFailed();
        }

        totalFeesCollected[token] += fee;

        emit FlashLoan(msg.sender, receiver, token, amount, fee);
        return true;
    }

    function _setFeeBps(uint256 newFeeBps) internal {
        if (newFeeBps > MAX_FEE_BPS) revert FeeTooHigh();
        feeBps = newFeeBps;
        emit FeeBpsUpdated(newFeeBps);
    }
}
