pragma solidity ^0.8.26;

import "../TestUtils.sol";
import "@src/executors/BalancerV2Executor.sol";
import {Constants} from "../Constants.sol";

contract BalancerV2ExecutorExposed is BalancerV2Executor {
    constructor() BalancerV2Executor() {}

    function decodeParams(bytes calldata data)
        external
        pure
        returns (address tokenIn, address tokenOut, bytes32 poolId)
    {
        return _decodeData(data);
    }
}

contract BalancerV2ExecutorTest is Constants, TestUtils {
    using SafeERC20 for IERC20;

    BalancerV2ExecutorExposed balancerV2Exposed;
    IERC20 WETH = IERC20(WETH_ADDR);
    IERC20 BAL = IERC20(BAL_ADDR);
    bytes32 constant WETH_BAL_POOL_ID =
        0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014;
    address private VAULT = 0xBA12222222228d8Ba445958a75a0704d566BF2C8;

    function setUp() public {
        uint256 forkBlock = 17323404;
        vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock);
        balancerV2Exposed = new BalancerV2ExecutorExposed();
    }

    function testDecodeParams() public view {
        bytes memory params =
            abi.encodePacked(WETH_ADDR, BAL_ADDR, WETH_BAL_POOL_ID);

        (address tokenIn, address tokenOut, bytes32 poolId) =
            balancerV2Exposed.decodeParams(params);

        assertEq(tokenIn, WETH_ADDR);
        assertEq(tokenOut, BAL_ADDR);
        assertEq(poolId, WETH_BAL_POOL_ID);
    }

    function testGetTransferData() public {
        bytes memory params =
            abi.encodePacked(WETH_ADDR, BAL_ADDR, WETH_BAL_POOL_ID);

        (
            TransferManager.TransferType transferType,
            address receiver,
            address tokenIn,
            address tokenOut,
            bool outputToRouter
        ) = balancerV2Exposed.getTransferData(params);

        assertEq(
            uint8(transferType),
            uint8(TransferManager.TransferType.ProtocolWillDebit)
        );
        assertEq(receiver, VAULT);
        assertEq(tokenIn, WETH_ADDR);
        assertEq(tokenOut, BAL_ADDR);
        assertEq(outputToRouter, false);
    }

    function testDecodeParamsInvalidDataLength() public {
        bytes memory invalidParams =
            abi.encodePacked(WETH_ADDR, BAL_ADDR, WETH_BAL_POOL_ID, address(2));

        vm.expectRevert(BalancerV2Executor__InvalidDataLength.selector);
        balancerV2Exposed.decodeParams(invalidParams);
    }

    function testSwap() public {
        uint256 amountIn = 10 ** 18;
        bytes memory protocolData =
            abi.encodePacked(WETH_ADDR, BAL_ADDR, WETH_BAL_POOL_ID);

        deal(WETH_ADDR, address(balancerV2Exposed), amountIn);
        uint256 balanceBefore = BAL.balanceOf(BOB);

        vm.prank(address(balancerV2Exposed));
        IERC20(WETH_ADDR).approve(VAULT, amountIn);
        balancerV2Exposed.swap(amountIn, protocolData, BOB);

        uint256 balanceAfter = BAL.balanceOf(BOB);
        assertGt(balanceAfter, balanceBefore);
    }

    function testDecodeIntegration() public view {
        bytes memory protocolData =
            loadCallDataFromFile("test_encode_balancer_v2");
        (address tokenIn, address tokenOut, bytes32 poolId) =
            balancerV2Exposed.decodeParams(protocolData);

        assertEq(tokenIn, WETH_ADDR);
        assertEq(tokenOut, BAL_ADDR);
        assertEq(poolId, WETH_BAL_POOL_ID);
    }

    function testSwapIntegration() public {
        // Generated by the SwapEncoder - test_encode_balancer_v2
        bytes memory protocolData =
            loadCallDataFromFile("test_encode_balancer_v2");

        uint256 amountIn = 10 ** 18;
        deal(WETH_ADDR, address(balancerV2Exposed), amountIn);
        uint256 balanceBefore = BAL.balanceOf(BOB);

        vm.prank(address(balancerV2Exposed));
        IERC20(WETH_ADDR).approve(VAULT, amountIn);
        balancerV2Exposed.swap(amountIn, protocolData, BOB);

        uint256 balanceAfter = BAL.balanceOf(BOB);
        assertGt(balanceAfter, balanceBefore);
    }
}
