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

import {Test} from "forge-std/Test.sol";
import "../../../contracts/bridge/trustless/EnhancedSwapRouterV2.sol";
import "../../../contracts/bridge/trustless/RouteTypesV2.sol";
import "../../../contracts/bridge/trustless/adapters/UniswapV3RouteExecutorAdapter.sol";
import "../../../contracts/bridge/trustless/adapters/BalancerRouteExecutorAdapter.sol";
import "../../../contracts/bridge/trustless/adapters/CurveRouteExecutorAdapter.sol";
import "../../../contracts/bridge/trustless/adapters/OneInchRouteExecutorAdapter.sol";
import "../../../contracts/bridge/trustless/pilot/Chain138PilotDexVenues.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract PilotVenueToken is ERC20 {
    constructor(string memory name_, string memory symbol_, uint256 supply) ERC20(name_, symbol_) {
        _mint(msg.sender, supply);
    }

    function mint(address to, uint256 amount) external {
        _mint(to, amount);
    }
}

contract Chain138PilotDexVenuesTest is Test {
    PilotVenueToken internal weth;
    PilotVenueToken internal usdt;
    PilotVenueToken internal usdc;
    PilotVenueToken internal dai;

    Chain138PilotUniswapV3Router internal uniswapRouter;
    Chain138PilotBalancerVault internal balancerVault;
    Chain138PilotCurve3Pool internal curvePool;
    Chain138PilotOneInchAggregationRouter internal oneInchRouter;
    EnhancedSwapRouterV2 internal router;

    address internal user = address(0x1234);
    bytes32 internal constant BALANCER_POOL_ID = keccak256("chain138-pilot-balancer-weth-usdc");

    function setUp() public {
        weth = new PilotVenueToken("Wrapped Ether", "WETH", 10_000 ether);
        usdt = new PilotVenueToken("Tether", "USDT", 50_000_000 * 1e6);
        usdc = new PilotVenueToken("USD Coin", "USDC", 50_000_000 * 1e6);
        dai = new PilotVenueToken("Dai", "DAI", 10_000_000 ether);

        uniswapRouter = new Chain138PilotUniswapV3Router();
        balancerVault = new Chain138PilotBalancerVault();
        curvePool = new Chain138PilotCurve3Pool(address(usdt), address(usdc), address(0), 4);
        oneInchRouter = new Chain138PilotOneInchAggregationRouter();

        router = new EnhancedSwapRouterV2(address(weth), address(usdt), address(usdc), address(dai));
        router.setProviderAdapter(RouteTypesV2.Provider.UniswapV3, address(new UniswapV3RouteExecutorAdapter()));
        router.setProviderAdapter(RouteTypesV2.Provider.Balancer, address(new BalancerRouteExecutorAdapter()));
        router.setProviderAdapter(RouteTypesV2.Provider.Curve, address(new CurveRouteExecutorAdapter()));
        router.setProviderAdapter(RouteTypesV2.Provider.OneInch, address(new OneInchRouteExecutorAdapter()));
        router.setProviderEnabled(RouteTypesV2.Provider.OneInch, true);

        weth.approve(address(uniswapRouter), type(uint256).max);
        usdt.approve(address(uniswapRouter), type(uint256).max);
        usdc.approve(address(uniswapRouter), type(uint256).max);
        weth.approve(address(balancerVault), type(uint256).max);
        usdt.approve(address(balancerVault), type(uint256).max);
        usdc.approve(address(balancerVault), type(uint256).max);
        usdt.approve(address(curvePool), type(uint256).max);
        usdc.approve(address(curvePool), type(uint256).max);
        weth.approve(address(oneInchRouter), type(uint256).max);
        usdt.approve(address(oneInchRouter), type(uint256).max);
        usdc.approve(address(oneInchRouter), type(uint256).max);

        uniswapRouter.seedPair(address(weth), address(usdt), 3000, 100 ether, 210_000 * 1e6);
        uniswapRouter.seedPair(address(weth), address(usdc), 3000, 100 ether, 210_000 * 1e6);
        balancerVault.seedPool(BALANCER_POOL_ID, address(weth), address(usdc), 100 ether, 210_000 * 1e6, 30);
        curvePool.fund(500_000 * 1e6, 500_000 * 1e6, 0);
        oneInchRouter.seedRoute(address(weth), address(usdt), 100 ether, 210_000 * 1e6, 35);

        router.setProviderRoute(
            address(weth),
            address(usdt),
            RouteTypesV2.Provider.UniswapV3,
            address(uniswapRouter),
            abi.encode(bytes(""), uint24(3000), address(uniswapRouter), false),
            true
        );
        router.setProviderRoute(
            address(weth),
            address(usdc),
            RouteTypesV2.Provider.Balancer,
            address(balancerVault),
            abi.encode(BALANCER_POOL_ID),
            true
        );
        router.setProviderRoute(
            address(usdt),
            address(usdc),
            RouteTypesV2.Provider.Curve,
            address(curvePool),
            abi.encode(int128(0), int128(1), false),
            true
        );
        router.setProviderRoute(
            address(weth),
            address(usdt),
            RouteTypesV2.Provider.OneInch,
            address(oneInchRouter),
            abi.encode(address(oneInchRouter), address(oneInchRouter), bytes("")),
            true
        );

        weth.mint(user, 10 ether);
        usdt.mint(user, 50_000 * 1e6);
    }

    function testUniswapPilotQuotesAndExecutes() public {
        uint256 quote = uniswapRouter.quoteExactInputSingle(address(weth), address(usdt), 3000, 1 ether, 0);
        assertGt(quote, 0);

        vm.startPrank(user);
        weth.approve(address(router), 1 ether);
        RouteTypesV2.RouteLeg[] memory legs = new RouteTypesV2.RouteLeg[](1);
        legs[0] = RouteTypesV2.RouteLeg({
            provider: RouteTypesV2.Provider.UniswapV3,
            tokenIn: address(weth),
            tokenOut: address(usdt),
            amountSource: RouteTypesV2.AmountSource.UserInput,
            minAmountOut: quote - 1,
            target: address(uniswapRouter),
            providerData: abi.encode(bytes(""), uint24(3000), address(uniswapRouter), false)
        });
        RouteTypesV2.RoutePlan memory plan = RouteTypesV2.RoutePlan({
            chainId: block.chainid,
            inputToken: address(weth),
            outputToken: address(usdt),
            amountIn: 1 ether,
            minAmountOut: quote - 1,
            recipient: user,
            deadline: block.timestamp + 300,
            legs: legs
        });
        uint256 amountOut = router.executeRoute(plan);
        vm.stopPrank();

        assertEq(amountOut, quote);
    }

    function testBalancerPilotExecutes() public {
        vm.startPrank(user);
        weth.approve(address(router), 1 ether);
        RouteTypesV2.RouteLeg[] memory legs = new RouteTypesV2.RouteLeg[](1);
        legs[0] = RouteTypesV2.RouteLeg({
            provider: RouteTypesV2.Provider.Balancer,
            tokenIn: address(weth),
            tokenOut: address(usdc),
            amountSource: RouteTypesV2.AmountSource.UserInput,
            minAmountOut: 1_000 * 1e6,
            target: address(balancerVault),
            providerData: abi.encode(BALANCER_POOL_ID)
        });
        RouteTypesV2.RoutePlan memory plan = RouteTypesV2.RoutePlan({
            chainId: block.chainid,
            inputToken: address(weth),
            outputToken: address(usdc),
            amountIn: 1 ether,
            minAmountOut: 1_000 * 1e6,
            recipient: user,
            deadline: block.timestamp + 300,
            legs: legs
        });
        uint256 amountOut = router.executeRoute(plan);
        vm.stopPrank();

        assertGt(amountOut, 0);
    }

    function testCurvePilotExecutes() public {
        vm.startPrank(user);
        usdt.approve(address(router), 10_000 * 1e6);
        RouteTypesV2.RouteLeg[] memory legs = new RouteTypesV2.RouteLeg[](1);
        legs[0] = RouteTypesV2.RouteLeg({
            provider: RouteTypesV2.Provider.Curve,
            tokenIn: address(usdt),
            tokenOut: address(usdc),
            amountSource: RouteTypesV2.AmountSource.UserInput,
            minAmountOut: 9_900 * 1e6,
            target: address(curvePool),
            providerData: abi.encode(int128(0), int128(1), false)
        });
        RouteTypesV2.RoutePlan memory plan = RouteTypesV2.RoutePlan({
            chainId: block.chainid,
            inputToken: address(usdt),
            outputToken: address(usdc),
            amountIn: 10_000 * 1e6,
            minAmountOut: 9_900 * 1e6,
            recipient: user,
            deadline: block.timestamp + 300,
            legs: legs
        });
        uint256 amountOut = router.executeRoute(plan);
        vm.stopPrank();

        assertGt(amountOut, 0);
    }

    function testOneInchPilotExecutes() public {
        vm.startPrank(user);
        weth.approve(address(router), 1 ether);
        RouteTypesV2.RouteLeg[] memory legs = new RouteTypesV2.RouteLeg[](1);
        legs[0] = RouteTypesV2.RouteLeg({
            provider: RouteTypesV2.Provider.OneInch,
            tokenIn: address(weth),
            tokenOut: address(usdt),
            amountSource: RouteTypesV2.AmountSource.UserInput,
            minAmountOut: 1_000 * 1e6,
            target: address(oneInchRouter),
            providerData: abi.encode(address(oneInchRouter), address(oneInchRouter), bytes(""))
        });
        RouteTypesV2.RoutePlan memory plan = RouteTypesV2.RoutePlan({
            chainId: block.chainid,
            inputToken: address(weth),
            outputToken: address(usdt),
            amountIn: 1 ether,
            minAmountOut: 1_000 * 1e6,
            recipient: user,
            deadline: block.timestamp + 300,
            legs: legs
        });
        uint256 amountOut = router.executeRoute(plan);
        vm.stopPrank();

        assertGt(amountOut, 0);
    }
}
