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

import {IERC3156FlashBorrower} from "@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol";
import {IERC3156FlashLender} from "@openzeppelin/contracts/interfaces/IERC3156FlashLender.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";

/// @notice Minimal Engine X ERC-3156 borrower for proving same-transaction USDC working capital.
/// @dev Prefund this contract with at least the flash fee before calling `runFlashProof`.
contract DBISEngineXFlashProofBorrower is IERC3156FlashBorrower, Ownable {
    using SafeERC20 for IERC20;

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

    IERC3156FlashLender public immutable lender;
    IERC20 public immutable usdc;

    mapping(bytes32 => bool) public usedProofIds;

    event EngineXFlashProof(
        bytes32 indexed proofId,
        address indexed initiator,
        address indexed lender,
        address token,
        uint256 amount,
        uint256 fee,
        bytes32 iso20022DocumentHash,
        bytes32 auditEnvelopeHash,
        bytes32 pegProofHash
    );

    constructor(address lender_, address usdc_, address owner_) Ownable(owner_) {
        require(lender_ != address(0) && usdc_ != address(0), "zero address");
        lender = IERC3156FlashLender(lender_);
        usdc = IERC20(usdc_);
    }

    function runFlashProof(
        uint256 amount,
        bytes32 proofId,
        bytes32 iso20022DocumentHash,
        bytes32 auditEnvelopeHash,
        bytes32 pegProofHash
    ) external onlyOwner returns (bool) {
        require(proofId != bytes32(0), "zero proof");
        require(!usedProofIds[proofId], "proof used");
        require(iso20022DocumentHash != bytes32(0), "zero iso hash");
        require(auditEnvelopeHash != bytes32(0), "zero audit hash");
        require(pegProofHash != bytes32(0), "zero peg hash");

        bytes memory data = abi.encode(msg.sender, proofId, iso20022DocumentHash, auditEnvelopeHash, pegProofHash);
        return lender.flashLoan(this, address(usdc), amount, data);
    }

    function onFlashLoan(address initiator, address token, uint256 amount, uint256 fee, bytes calldata data)
        external
        override
        returns (bytes32)
    {
        require(msg.sender == address(lender), "bad lender");
        require(initiator == address(this), "bad initiator");
        require(token == address(usdc), "bad token");

        (
            address operator,
            bytes32 proofId,
            bytes32 iso20022DocumentHash,
            bytes32 auditEnvelopeHash,
            bytes32 pegProofHash
        ) = abi.decode(data, (address, bytes32, bytes32, bytes32, bytes32));
        require(!usedProofIds[proofId], "proof used");
        usedProofIds[proofId] = true;

        usdc.forceApprove(msg.sender, amount + fee);
        emit EngineXFlashProof(
            proofId, operator, msg.sender, token, amount, fee, iso20022DocumentHash, auditEnvelopeHash, pegProofHash
        );
        return _RETURN_VALUE;
    }

    function withdraw(address token, address to, uint256 amount) external onlyOwner {
        require(to != address(0), "zero to");
        IERC20(token).safeTransfer(to, amount);
    }
}
