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

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "../RouteTypesV2.sol";
import "../interfaces/IRouteExecutorAdapter.sol";

interface ID3ProxyView {
    function _DODO_APPROVE_PROXY_() external view returns (address);

    function sellTokens(
        address pool,
        address to,
        address fromToken,
        address toToken,
        uint256 fromAmount,
        uint256 minReceiveAmount,
        bytes calldata data,
        uint256 deadLine
    ) external payable returns (uint256 receiveToAmount);
}

interface IDODOApproveProxyView {
    function _DODO_APPROVE_() external view returns (address);
}

interface ID3MMQuoter {
    function querySellTokens(
        address fromToken,
        address toToken,
        uint256 fromAmount
    ) external view returns (uint256 payFromAmount, uint256 receiveToAmount, uint256 vusdAmount, uint256 swapFee, uint256 mtFee);
}

contract DodoV3RouteExecutorAdapter is IRouteExecutorAdapter {
    using SafeERC20 for IERC20;

    uint256 internal constant DEFAULT_GAS_ESTIMATE = 330000;

    function validate(
        RouteTypesV2.RouteLeg calldata leg
    ) external view override returns (bool ok, string memory reason) {
        if (leg.provider != RouteTypesV2.Provider.DodoV3) {
            return (false, "DodoV3RouteExecutorAdapter: invalid provider");
        }
        if (leg.target == address(0)) {
            return (false, "DodoV3RouteExecutorAdapter: zero target");
        }
        if (leg.tokenIn == address(0) || leg.tokenOut == address(0)) {
            return (false, "DodoV3RouteExecutorAdapter: zero token");
        }
        if (leg.providerData.length != 32) {
            return (false, "DodoV3RouteExecutorAdapter: invalid providerData");
        }

        address poolAddress = abi.decode(leg.providerData, (address));
        if (poolAddress == address(0)) {
            return (false, "DodoV3RouteExecutorAdapter: zero pool");
        }

        address approveProxy = ID3ProxyView(leg.target)._DODO_APPROVE_PROXY_();
        if (approveProxy == address(0)) {
            return (false, "DodoV3RouteExecutorAdapter: zero approve proxy");
        }
        address approve = IDODOApproveProxyView(approveProxy)._DODO_APPROVE_();
        if (approve == address(0)) {
            return (false, "DodoV3RouteExecutorAdapter: zero approve");
        }

        return (true, "");
    }

    function quote(
        RouteTypesV2.RouteLeg calldata leg,
        uint256 amountIn
    ) external view override returns (uint256 amountOut, uint256 gasEstimate) {
        address poolAddress = abi.decode(leg.providerData, (address));
        (, amountOut,,,) = ID3MMQuoter(poolAddress).querySellTokens(leg.tokenIn, leg.tokenOut, amountIn);
        gasEstimate = DEFAULT_GAS_ESTIMATE;
    }

    function execute(
        RouteTypesV2.RouteLeg calldata leg,
        uint256 amountIn
    ) external override returns (uint256 amountOut) {
        address poolAddress = abi.decode(leg.providerData, (address));
        address approveProxy = ID3ProxyView(leg.target)._DODO_APPROVE_PROXY_();
        address approve = IDODOApproveProxyView(approveProxy)._DODO_APPROVE_();

        IERC20(leg.tokenIn).forceApprove(approve, 0);
        IERC20(leg.tokenIn).forceApprove(approve, amountIn);

        amountOut = ID3ProxyView(leg.target).sellTokens(
            poolAddress,
            address(this),
            leg.tokenIn,
            leg.tokenOut,
            amountIn,
            leg.minAmountOut,
            bytes(""),
            block.timestamp + 300
        );

        IERC20(leg.tokenIn).forceApprove(approve, 0);
        IERC20(leg.tokenOut).safeTransfer(msg.sender, amountOut);
    }
}
