// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/interfaces/IERC20.sol";
import "./lib/seaport/SeaportInterface.sol";

contract TMSeaportProxy is Ownable, Pausable {
    SeaportInterface seaport;
    address kyberRouter;
    address constant NativeAddress = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
    error InsufficientBalance(
        address token,
        uint256 available,
        uint256 required
    );
    error SwapFailed();

    error WrongInput();

    constructor(SeaportInterface _seaport, address _kyberRouter) {
        seaport = _seaport;
        kyberRouter = _kyberRouter;
    }

    function changeSeaport(
        SeaportInterface _seaport
    ) external onlyOwner whenPaused {
        seaport = _seaport;
    }

    function changeRouter(address _kyberRouter) external onlyOwner whenPaused {
        kyberRouter = _kyberRouter;
    }

    receive() external payable {}

    function unpause() external onlyOwner {
        _unpause();
    }

    function pause() external onlyOwner {
        _pause();
    }

    function approveToken(
        address token,
        address spender,
        uint amount
    ) external onlyOwner {
        IERC20(token).approve(spender, amount);
    }

    function rescueFunds(IERC20 token, uint256 amount) external onlyOwner {
        if (address(token) == NativeAddress) {
            payable(msg.sender).transfer(amount);
        } else {
            token.transfer(msg.sender, amount);
        }
    }

    function swapToFulfill(
        AdvancedOrder calldata advancedOrder,
        CriteriaResolver[] calldata criteriaResolvers,
        bytes32 fulfillerConduitKey,
        address recipient,
        bytes calldata swapData,
        address tokenIn,
        uint256 amountIn
    ) external payable whenNotPaused {
        // check consideration items
        address token;
        uint256 fulfillAmount = 0;
        uint256 returnAmount = 0;
        {
            if (tokenIn != NativeAddress) {
                IERC20(tokenIn).transferFrom(
                    msg.sender,
                    address(this),
                    amountIn
                );
                IERC20(tokenIn).approve(kyberRouter, amountIn);
            }
            //
            ConsiderationItem[] memory considerations = advancedOrder
                .parameters
                .consideration;

            token = address(considerations[0].token);
            for (uint i = 0; i < considerations.length; i++) {
                if (
                    considerations[i].token != token ||
                    considerations[i].startAmount != considerations[i].endAmount
                ) {
                    revert WrongInput();
                }
                fulfillAmount += considerations[i].endAmount;
            }
            fulfillAmount =
                (fulfillAmount * advancedOrder.numerator) /
                advancedOrder.denominator;

            //swap to consideration token
            (bool success, bytes memory data) = kyberRouter.call{
                value: msg.value
            }(swapData);

            if (!success) {
                revert SwapFailed();
            }
            (returnAmount, ) = abi.decode(data, (uint256, uint256));

            if (returnAmount < fulfillAmount) {
                revert InsufficientBalance(
                    address(considerations[0].token),
                    returnAmount,
                    fulfillAmount
                );
            }
        }
        // fulfill order!
        seaport.fulfillAdvancedOrder(
            advancedOrder,
            criteriaResolvers,
            fulfillerConduitKey,
            recipient
        );
        if (returnAmount > fulfillAmount)
            IERC20(token).transfer(msg.sender, returnAmount - fulfillAmount);
    }
}
[{"inputs":[{"internalType":"contract SeaportInterface","name":"_seaport","type":"address"},{"internalType":"address","name":"_kyberRouter","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"available","type":"uint256"},{"internalType":"uint256","name":"required","type":"uint256"}],"name":"InsufficientBalance","type":"error"},{"inputs":[],"name":"SwapFailed","type":"error"},{"inputs":[],"name":"WrongInput","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approveToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_kyberRouter","type":"address"}],"name":"changeRouter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract SeaportInterface","name":"_seaport","type":"address"}],"name":"changeSeaport","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"rescueFunds","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"address","name":"offerer","type":"address"},{"internalType":"address","name":"zone","type":"address"},{"components":[{"internalType":"enum ItemType","name":"itemType","type":"uint8"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"identifierOrCriteria","type":"uint256"},{"internalType":"uint256","name":"startAmount","type":"uint256"},{"internalType":"uint256","name":"endAmount","type":"uint256"}],"internalType":"struct OfferItem[]","name":"offer","type":"tuple[]"},{"components":[{"internalType":"enum ItemType","name":"itemType","type":"uint8"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"identifierOrCriteria","type":"uint256"},{"internalType":"uint256","name":"startAmount","type":"uint256"},{"internalType":"uint256","name":"endAmount","type":"uint256"},{"internalType":"address payable","name":"recipient","type":"address"}],"internalType":"struct ConsiderationItem[]","name":"consideration","type":"tuple[]"},{"internalType":"enum OrderType","name":"orderType","type":"uint8"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"bytes32","name":"zoneHash","type":"bytes32"},{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"bytes32","name":"conduitKey","type":"bytes32"},{"internalType":"uint256","name":"totalOriginalConsiderationItems","type":"uint256"}],"internalType":"struct OrderParameters","name":"parameters","type":"tuple"},{"internalType":"uint120","name":"numerator","type":"uint120"},{"internalType":"uint120","name":"denominator","type":"uint120"},{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"bytes","name":"extraData","type":"bytes"}],"internalType":"struct AdvancedOrder","name":"advancedOrder","type":"tuple"},{"components":[{"internalType":"uint256","name":"orderIndex","type":"uint256"},{"internalType":"enum Side","name":"side","type":"uint8"},{"internalType":"uint256","name":"index","type":"uint256"},{"internalType":"uint256","name":"identifier","type":"uint256"},{"internalType":"bytes32[]","name":"criteriaProof","type":"bytes32[]"}],"internalType":"struct CriteriaResolver[]","name":"criteriaResolvers","type":"tuple[]"},{"internalType":"bytes32","name":"fulfillerConduitKey","type":"bytes32"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"bytes","name":"swapData","type":"bytes"},{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"}],"name":"swapToFulfill","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]