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

import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "./RouteTypesV2.sol";
import "./interfaces/IEnhancedSwapRouterV2.sol";
import "./interfaces/IBridgeIntentExecutor.sol";

contract IntentBridgeCoordinatorV2 is AccessControl, ReentrancyGuard {
    using SafeERC20 for IERC20;

    bytes32 public constant EXECUTOR_MANAGER_ROLE = keccak256("EXECUTOR_MANAGER_ROLE");

    IEnhancedSwapRouterV2 public immutable swapRouter;
    mapping(bytes32 => address) public bridgeExecutors;

    event BridgeExecutorSet(bytes32 indexed bridgeType, address indexed executor);
    event IntentExecuted(
        bytes32 indexed intentHash,
        bytes32 indexed bridgeReference,
        bytes32 indexed destinationPlanHash,
        address inputToken,
        address bridgeToken,
        uint256 amountIn,
        uint256 bridgedAmount,
        address recipient
    );

    error ZeroAddress();
    error InvalidIntent();
    error BridgeExecutorNotConfigured();
    error BridgeValidationFailed(string reason);

    constructor(address _swapRouter) {
        if (_swapRouter == address(0)) revert ZeroAddress();
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _grantRole(EXECUTOR_MANAGER_ROLE, msg.sender);
        swapRouter = IEnhancedSwapRouterV2(_swapRouter);
    }

    function setBridgeExecutor(
        bytes32 bridgeType,
        address executor
    ) external onlyRole(EXECUTOR_MANAGER_ROLE) {
        if (executor == address(0)) revert ZeroAddress();
        bridgeExecutors[bridgeType] = executor;
        emit BridgeExecutorSet(bridgeType, executor);
    }

    function executeIntent(
        RouteTypesV2.BridgeIntentPlan calldata intent
    ) external payable nonReentrant returns (bytes32 bridgeReference, bytes32 destinationPlanHash) {
        if (intent.deadline < block.timestamp) revert InvalidIntent();
        if (intent.recipient == address(0)) revert InvalidIntent();
        if (intent.sourcePlan.deadline < block.timestamp) revert InvalidIntent();
        if (intent.sourcePlan.recipient != address(this)) revert InvalidIntent();

        address bridgeExecutor = bridgeExecutors[intent.bridgeType];
        if (bridgeExecutor == address(0)) revert BridgeExecutorNotConfigured();

        uint256 bridgedAmount;
        address bridgeToken;
        if (intent.sourcePlan.inputToken == address(0)) {
            bridgedAmount = swapRouter.executeRoute{value: intent.sourcePlan.amountIn}(intent.sourcePlan);
            bridgeToken = intent.sourcePlan.outputToken;
        } else {
            IERC20(intent.sourcePlan.inputToken).safeTransferFrom(
                msg.sender,
                address(this),
                intent.sourcePlan.amountIn
            );
            IERC20(intent.sourcePlan.inputToken).forceApprove(address(swapRouter), 0);
            IERC20(intent.sourcePlan.inputToken).forceApprove(address(swapRouter), intent.sourcePlan.amountIn);
            bridgedAmount = swapRouter.executeRoute(intent.sourcePlan);
            bridgeToken = intent.sourcePlan.outputToken;
        }

        (bool ok, string memory reason) = IBridgeIntentExecutor(bridgeExecutor).validateBridge(
            intent.bridgeType,
            intent.bridgeData,
            bridgeToken,
            bridgedAmount,
            intent.recipient
        );
        if (!ok) revert BridgeValidationFailed(reason);

        IERC20(bridgeToken).forceApprove(bridgeExecutor, 0);
        IERC20(bridgeToken).forceApprove(bridgeExecutor, bridgedAmount);

        bridgeReference = IBridgeIntentExecutor(bridgeExecutor).executeBridge(
            intent.bridgeType,
            intent.bridgeData,
            bridgeToken,
            bridgedAmount,
            intent.recipient
        );
        destinationPlanHash = keccak256(
            abi.encode(
                intent.destinationPlan.chainId,
                intent.destinationPlan.inputToken,
                intent.destinationPlan.outputToken,
                intent.destinationPlan.amountIn,
                intent.destinationPlan.minAmountOut,
                intent.destinationPlan.recipient,
                intent.destinationPlan.deadline,
                intent.destinationPlan.legs
            )
        );

        emit IntentExecuted(
            keccak256(abi.encode(intent.bridgeType, intent.recipient, intent.deadline)),
            bridgeReference,
            destinationPlanHash,
            intent.sourcePlan.inputToken == address(0) ? address(0) : intent.sourcePlan.inputToken,
            bridgeToken,
            intent.sourcePlan.amountIn,
            bridgedAmount,
            intent.recipient
        );
    }

    receive() external payable {}
}
