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

contract BalancerRouteExecutorAdapter 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.Balancer) {
            return (false, "BalancerRouteExecutorAdapter: invalid provider");
        }
        if (leg.target == address(0)) {
            return (false, "BalancerRouteExecutorAdapter: zero target");
        }
        if (leg.providerData.length != 32) {
            return (false, "BalancerRouteExecutorAdapter: invalid providerData");
        }
        return (true, "");
    }

    function quote(
        RouteTypesV2.RouteLeg calldata leg,
        uint256 amountIn
    ) external view override returns (uint256 amountOut, uint256 gasEstimate) {
        bytes32 poolId = abi.decode(leg.providerData, (bytes32));
        (address[] memory tokens, uint256[] memory balances,) = IBalancerVault(leg.target).getPoolTokens(poolId);
        uint256 inIndex = type(uint256).max;
        uint256 outIndex = type(uint256).max;

        for (uint256 i = 0; i < tokens.length; i++) {
            if (tokens[i] == leg.tokenIn) inIndex = i;
            if (tokens[i] == leg.tokenOut) outIndex = i;
        }

        if (inIndex != type(uint256).max && outIndex != type(uint256).max && balances[inIndex] > 0) {
            uint256 grossOut = (amountIn * balances[outIndex]) / balances[inIndex];
            amountOut = (grossOut * 9950) / 10000;
        }

        gasEstimate = 230000;
    }

    function execute(
        RouteTypesV2.RouteLeg calldata leg,
        uint256 amountIn
    ) external override returns (uint256 amountOut) {
        bytes32 poolId = abi.decode(leg.providerData, (bytes32));
        IERC20(leg.tokenIn).forceApprove(leg.target, 0);
        IERC20(leg.tokenIn).forceApprove(leg.target, amountIn);

        amountOut = IBalancerVault(leg.target).swap(
            IBalancerVault.SingleSwap({
                poolId: poolId,
                kind: IBalancerVault.SwapKind.GIVEN_IN,
                assetIn: leg.tokenIn,
                assetOut: leg.tokenOut,
                amount: amountIn,
                userData: ""
            }),
            IBalancerVault.FundManagement({
                sender: address(this),
                fromInternalBalance: false,
                recipient: payable(msg.sender),
                toInternalBalance: false
            }),
            leg.minAmountOut,
            block.timestamp + 300
        );
    }
}
