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

import "../ccip/IRouterClient.sol";
import {Client} from "@chainlink/contracts-ccip/contracts/libraries/Client.sol";
import {IAny2EVMMessageReceiver} from "@chainlink/contracts-ccip/contracts/interfaces/IAny2EVMMessageReceiver.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";

/**
 * @title CCIP Relay Router
 * @notice Relay router that forwards CCIP messages from off-chain relay to bridge contracts
 * @dev This contract acts as a relay endpoint on the destination chain
 */
contract CCIPRelayRouter is AccessControl {
    bytes32 public constant RELAYER_ROLE = keccak256("RELAYER_ROLE");
    
    // Mapping of bridge contracts that can receive messages
    mapping(address => bool) public authorizedBridges;
    
    event MessageRelayed(
        bytes32 indexed messageId,
        uint64 indexed sourceChainSelector,
        address indexed bridge,
        address recipient,
        uint256 amount
    );
    
    event BridgeAuthorized(address indexed bridge);
    event BridgeRevoked(address indexed bridge);

    constructor() {
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
    }
    
    /**
     * @notice Authorize a bridge contract to receive relayed messages
     */
    function authorizeBridge(address bridge) external onlyRole(DEFAULT_ADMIN_ROLE) {
        authorizedBridges[bridge] = true;
        emit BridgeAuthorized(bridge);
    }
    
    /**
     * @notice Revoke authorization for a bridge contract
     */
    function revokeBridge(address bridge) external onlyRole(DEFAULT_ADMIN_ROLE) {
        authorizedBridges[bridge] = false;
        emit BridgeRevoked(bridge);
    }
    
    /**
     * @notice Grant relayer role to an address
     */
    function grantRelayerRole(address relayer) external onlyRole(DEFAULT_ADMIN_ROLE) {
        _grantRole(RELAYER_ROLE, relayer);
    }
    
    /**
     * @notice Revoke relayer role from an address
     */
    function revokeRelayerRole(address relayer) external onlyRole(DEFAULT_ADMIN_ROLE) {
        _revokeRole(RELAYER_ROLE, relayer);
    }
    
    /**
     * @notice Relay a CCIP message to a bridge contract
     * @param bridge The bridge contract address to receive the message
     * @param message The CCIP message to relay
     */
    function relayMessage(
        address bridge,
        IRouterClient.Any2EVMMessage calldata message
    ) external onlyRole(RELAYER_ROLE) {
        require(authorizedBridges[bridge], "CCIPRelayRouter: bridge not authorized");

        // CWMultiTokenBridgeL2 and official CCIP receivers use Client.Any2EVMMessage
        // (destTokenAmounts: EVMTokenAmount[]). Legacy CCIPRelayBridge uses IRouterClient
        // Any2EVMMessage (tokenAmounts with amountType). Try Client format first, then legacy.
        Client.Any2EVMMessage memory clientMessage = _toClientMessage(message);
        (bool success, bytes memory returnData) = bridge.call(
            abi.encodeCall(IAny2EVMMessageReceiver.ccipReceive, (clientMessage))
        );
        if (!success) {
            (success, returnData) = bridge.call(
                abi.encodeWithSignature(
                    "ccipReceive((bytes32,uint64,bytes,bytes,(address,uint256,uint8)[]))",
                    message
                )
            );
        }
        if (!success && returnData.length > 0) {
            assembly {
                revert(add(returnData, 32), mload(returnData))
            }
        }
        require(success, "CCIPRelayRouter: ccipReceive failed");
        
        // If we get here, the call succeeded. Decode common payload shapes without
        // reverting the full relay transaction on non-WETH bridge payloads.
        (address recipient, uint256 amount) = _decodeRecipientAndAmount(message.data);
        
        emit MessageRelayed(
            message.messageId,
            message.sourceChainSelector,
            bridge,
            recipient,
            amount
        );
    }

    function _decodeRecipientAndAmount(bytes calldata data)
        internal
        pure
        returns (address recipient, uint256 amount)
    {
        if (data.length == 64) {
            return abi.decode(data, (address, uint256));
        }

        if (data.length == 96) {
            (, recipient, amount) = abi.decode(data, (address, address, uint256));
            return (recipient, amount);
        }

        if (data.length == 128) {
            (recipient, amount, , ) = abi.decode(data, (address, uint256, address, uint256));
            return (recipient, amount);
        }

        return (address(0), 0);
    }

    function _toClientMessage(
        IRouterClient.Any2EVMMessage calldata message
    ) internal pure returns (Client.Any2EVMMessage memory clientMessage) {
        Client.EVMTokenAmount[] memory destAmounts = new Client.EVMTokenAmount[](message.tokenAmounts.length);
        for (uint256 i = 0; i < message.tokenAmounts.length; i++) {
            destAmounts[i] = Client.EVMTokenAmount({
                token: message.tokenAmounts[i].token,
                amount: message.tokenAmounts[i].amount
            });
        }
        clientMessage = Client.Any2EVMMessage({
            messageId: message.messageId,
            sourceChainSelector: message.sourceChainSelector,
            sender: message.sender,
            data: message.data,
            destTokenAmounts: destAmounts
        });
    }
}
