// 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 "../interfaces/ISwapRouter.sol";
import "../RouteTypesV2.sol";
import "../interfaces/IRouteExecutorAdapter.sol";

contract UniswapV3RouteExecutorAdapter is IRouteExecutorAdapter {
    using SafeERC20 for IERC20;

    function validate(
        RouteTypesV2.RouteLeg calldata leg
    ) external pure override returns (bool ok, string memory reason) {
        if (leg.provider != RouteTypesV2.Provider.UniswapV3) {
            return (false, "UniswapV3RouteExecutorAdapter: invalid provider");
        }
        if (leg.target == address(0)) {
            return (false, "UniswapV3RouteExecutorAdapter: zero target");
        }
        if (leg.providerData.length == 0) {
            return (false, "UniswapV3RouteExecutorAdapter: missing providerData");
        }
        return (true, "");
    }

    function quote(
        RouteTypesV2.RouteLeg calldata leg,
        uint256 amountIn
    ) external view override returns (uint256 amountOut, uint256 gasEstimate) {
        (bytes memory path, uint24 fee, address quoter, bool usePath) =
            abi.decode(leg.providerData, (bytes, uint24, address, bool));

        if (quoter != address(0)) {
            bytes memory data;
            bool ok;
            if (usePath) {
                (ok, data) = quoter.staticcall(
                    abi.encodeWithSignature("quoteExactInput(bytes,uint256)", path, amountIn)
                );
            } else {
                (ok, data) = quoter.staticcall(
                    abi.encodeWithSignature(
                        "quoteExactInputSingle(address,address,uint24,uint256,uint160)",
                        leg.tokenIn,
                        leg.tokenOut,
                        fee,
                        amountIn,
                        uint160(0)
                    )
                );
            }
            if (ok && data.length >= 32) {
                amountOut = abi.decode(data, (uint256));
            }
        }

        gasEstimate = usePath ? 220000 : 175000;
    }

    function execute(
        RouteTypesV2.RouteLeg calldata leg,
        uint256 amountIn
    ) external override returns (uint256 amountOut) {
        (bytes memory path, uint24 fee,, bool usePath) =
            abi.decode(leg.providerData, (bytes, uint24, address, bool));

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

        if (usePath) {
            amountOut = ISwapRouter(leg.target).exactInput(
                ISwapRouter.ExactInputParams({
                    path: path,
                    recipient: msg.sender,
                    deadline: block.timestamp + 300,
                    amountIn: amountIn,
                    amountOutMinimum: leg.minAmountOut
                })
            );
        } else {
            amountOut = ISwapRouter(leg.target).exactInputSingle(
                ISwapRouter.ExactInputSingleParams({
                    tokenIn: leg.tokenIn,
                    tokenOut: leg.tokenOut,
                    fee: fee,
                    recipient: msg.sender,
                    deadline: block.timestamp + 300,
                    amountIn: amountIn,
                    amountOutMinimum: leg.minAmountOut,
                    sqrtPriceLimitX96: 0
                })
            );
        }
    }
}
