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

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ISwapRouter} from "../bridge/trustless/interfaces/ISwapRouter.sol";

interface IDODOTwoHopSwapExactIn {
    function swapExactIn(address pool, address tokenIn, uint256 amountIn, uint256 minAmountOut)
        external
        returns (uint256 amountOut);
}

/**
 * @title TwoHopDodoToUniswapV3MultiHopExternalUnwinder
 * @notice Unwinds through two DODO PMM hops followed by a final Uniswap V3 exactInput path.
 * @dev `data` =
 *      abi.encode(
 *          address poolA,
 *          address poolB,
 *          address midToken,
 *          uint256 minMidOut,
 *          address intermediateToken,
 *          uint256 minIntermediateOut,
 *          bytes uniswapPath
 *      )
 *      Route shape:
 *        tokenIn --(poolA via DODO)--> midToken
 *        midToken --(poolB via DODO)--> intermediateToken
 *        intermediateToken --(Uniswap V3 path)--> tokenOut
 */
contract TwoHopDodoToUniswapV3MultiHopExternalUnwinder {
    using SafeERC20 for IERC20;

    address public immutable integration;
    address public immutable router;

    error BadParams();

    constructor(address integration_, address router_) {
        if (integration_ == address(0) || router_ == address(0)) revert BadParams();
        integration = integration_;
        router = router_;
    }

    function unwind(address tokenIn, address tokenOut, uint256 amountIn, uint256 minAmountOut, bytes calldata data)
        external
        returns (uint256 amountOut)
    {
        if (tokenIn == address(0) || tokenOut == address(0) || tokenIn == tokenOut || amountIn == 0) revert BadParams();

        (
            address poolA,
            address poolB,
            address midToken,
            uint256 minMidOut,
            address intermediateToken,
            uint256 minIntermediateOut,
            bytes memory uniswapPath
        ) = abi.decode(data, (address, address, address, uint256, address, uint256, bytes));

        if (poolA == address(0) || poolB == address(0) || midToken == address(0) || intermediateToken == address(0)) {
            revert BadParams();
        }
        if (midToken == tokenIn || midToken == tokenOut || intermediateToken == tokenIn || intermediateToken == midToken) {
            revert BadParams();
        }
        if (uniswapPath.length < 43) revert BadParams();

        IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn);
        IERC20(tokenIn).forceApprove(integration, amountIn);

        uint256 midOut = IDODOTwoHopSwapExactIn(integration).swapExactIn(poolA, tokenIn, amountIn, minMidOut);
        if (midOut == 0) revert BadParams();

        IERC20(midToken).forceApprove(integration, midOut);
        uint256 intermediateOut =
            IDODOTwoHopSwapExactIn(integration).swapExactIn(poolB, midToken, midOut, minIntermediateOut);
        if (intermediateOut == 0) revert BadParams();

        IERC20(intermediateToken).forceApprove(router, intermediateOut);
        amountOut = ISwapRouter(router).exactInput(
            ISwapRouter.ExactInputParams({
                path: uniswapPath,
                recipient: msg.sender,
                deadline: block.timestamp + 300,
                amountIn: intermediateOut,
                amountOutMinimum: minAmountOut
            })
        );
    }
}
