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

// contracts/ccip/IRouterClient.sol

/**
 * @title Chainlink CCIP Router Client Interface
 * @notice Interface for Chainlink CCIP Router Client
 * @dev This interface is based on Chainlink CCIP Router Client specification
 */
interface IRouterClient {
    /// @notice Represents the router's fee token
    enum TokenAmountType {
        Fiat,
        Native
    }

    /// @notice Represents a token amount and its type
    struct TokenAmount {
        address token;
        uint256 amount;
        TokenAmountType amountType;
    }

    /// @notice Represents a CCIP message
    struct EVM2AnyMessage {
        bytes receiver;
        bytes data;
        TokenAmount[] tokenAmounts;
        address feeToken;
        bytes extraArgs;
    }

    /// @notice Represents a CCIP message with source chain information
    struct Any2EVMMessage {
        bytes32 messageId;
        uint64 sourceChainSelector;
        bytes sender;
        bytes data;
        TokenAmount[] tokenAmounts;
    }

    /// @notice Emitted when a message is sent
    event MessageSent(
        bytes32 indexed messageId,
        uint64 indexed destinationChainSelector,
        address indexed sender,
        bytes receiver,
        bytes data,
        TokenAmount[] tokenAmounts,
        address feeToken,
        bytes extraArgs
    );

    /// @notice Emitted when a message is received
    event MessageReceived(
        bytes32 indexed messageId,
        uint64 indexed sourceChainSelector,
        address indexed sender,
        bytes data,
        TokenAmount[] tokenAmounts
    );

    /// @notice Sends a message to a destination chain
    /// @param destinationChainSelector The chain selector of the destination chain
    /// @param message The message to send
    /// @return messageId The ID of the sent message
    /// @return fees The fees required for the message
    /// @dev If feeToken is zero address, fees are paid in native token (ETH) via msg.value
    function ccipSend(
        uint64 destinationChainSelector,
        EVM2AnyMessage memory message
    ) external payable returns (bytes32 messageId, uint256 fees);

    /// @notice Gets the fee for sending a message
    /// @param destinationChainSelector The chain selector of the destination chain
    /// @param message The message to send
    /// @return fee The fee required for the message
    function getFee(
        uint64 destinationChainSelector,
        EVM2AnyMessage memory message
    ) external view returns (uint256 fee);

    /// @notice Gets the supported tokens for a destination chain
    /// @param destinationChainSelector The chain selector of the destination chain
    /// @return tokens The list of supported tokens
    function getSupportedTokens(
        uint64 destinationChainSelector
    ) external view returns (address[] memory tokens);
}

// contracts/ccip/CCIPWETH9Bridge.sol

// Minimal IERC20 interface for ERC20 tokens (WETH9 and LINK)
interface IERC20 {
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
    function transfer(address to, uint256 amount) external returns (bool);
    function approve(address spender, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
}

/**
 * @title CCIP WETH9 Bridge
 * @notice Cross-chain WETH9 transfer bridge using Chainlink CCIP
 * @dev Enables users to send WETH9 tokens across chains via CCIP
 */
contract CCIPWETH9Bridge {
    
    IRouterClient public immutable ccipRouter;
    address public immutable weth9; // WETH9 contract address
    address public feeToken; // LINK token address
    address public admin;
    
    // Destination chain configurations
    struct DestinationChain {
        uint64 chainSelector;
        address receiverBridge; // Address of corresponding bridge on destination chain
        bool enabled;
    }
    
    mapping(uint64 => DestinationChain) public destinations;
    uint64[] public destinationChains;
    
    // Track cross-chain transfers for replay protection
    mapping(bytes32 => bool) public processedTransfers;
    mapping(address => uint256) public nonces;
    
    event CrossChainTransferInitiated(
        bytes32 indexed messageId,
        address indexed sender,
        uint64 indexed destinationChainSelector,
        address recipient,
        uint256 amount,
        uint256 nonce
    );
    
    event CrossChainTransferCompleted(
        bytes32 indexed messageId,
        uint64 indexed sourceChainSelector,
        address indexed recipient,
        uint256 amount
    );
    
    event DestinationAdded(uint64 chainSelector, address receiverBridge);
    event DestinationRemoved(uint64 chainSelector);
    event DestinationUpdated(uint64 chainSelector, address receiverBridge);
    
    modifier onlyAdmin() {
        require(msg.sender == admin, "CCIPWETH9Bridge: only admin");
        _;
    }
    
    modifier onlyRouter() {
        require(msg.sender == address(ccipRouter), "CCIPWETH9Bridge: only router");
        _;
    }
    
    constructor(address _ccipRouter, address _weth9, address _feeToken) {
        require(_ccipRouter != address(0), "CCIPWETH9Bridge: zero router");
        require(_weth9 != address(0), "CCIPWETH9Bridge: zero WETH9");
        require(_feeToken != address(0), "CCIPWETH9Bridge: zero fee token");
        
        ccipRouter = IRouterClient(_ccipRouter);
        weth9 = _weth9;
        feeToken = _feeToken;
        admin = msg.sender;
    }
    
    /**
     * @notice Send WETH9 tokens to another chain via CCIP
     * @param destinationChainSelector The chain selector of the destination chain
     * @param recipient The recipient address on the destination chain
     * @param amount The amount of WETH9 to send
     * @return messageId The CCIP message ID
     */
    function sendCrossChain(
        uint64 destinationChainSelector,
        address recipient,
        uint256 amount
    ) external returns (bytes32 messageId) {
        require(amount > 0, "CCIPWETH9Bridge: invalid amount");
        require(recipient != address(0), "CCIPWETH9Bridge: zero recipient");
        
        DestinationChain memory dest = destinations[destinationChainSelector];
        require(dest.enabled, "CCIPWETH9Bridge: destination not enabled");
        
        // Transfer WETH9 from user
        require(IERC20(weth9).transferFrom(msg.sender, address(this), amount), "CCIPWETH9Bridge: transfer failed");
        
        // Increment nonce for replay protection
        nonces[msg.sender]++;
        uint256 currentNonce = nonces[msg.sender];
        
        // Encode transfer data (recipient, amount, sender, nonce)
        bytes memory data = abi.encode(
            recipient,
            amount,
            msg.sender,
            currentNonce
        );
        
        // Prepare CCIP message with WETH9 tokens
        IRouterClient.EVM2AnyMessage memory message = IRouterClient.EVM2AnyMessage({
            receiver: abi.encode(dest.receiverBridge),
            data: data,
            tokenAmounts: new IRouterClient.TokenAmount[](1),
            feeToken: feeToken,
            extraArgs: ""
        });
        
        // Set token amount (WETH9)
        message.tokenAmounts[0] = IRouterClient.TokenAmount({
            token: weth9,
            amount: amount,
            amountType: IRouterClient.TokenAmountType.Fiat
        });
        
        // Calculate fee
        uint256 fee = ccipRouter.getFee(destinationChainSelector, message);
        
        // Approve and pay fee
        if (fee > 0) {
            require(IERC20(feeToken).transferFrom(msg.sender, address(this), fee), "CCIPWETH9Bridge: fee transfer failed");
            require(IERC20(feeToken).approve(address(ccipRouter), fee), "CCIPWETH9Bridge: fee approval failed");
        }
        
        // Send via CCIP
        (messageId, ) = ccipRouter.ccipSend(destinationChainSelector, message);
        
        emit CrossChainTransferInitiated(
            messageId,
            msg.sender,
            destinationChainSelector,
            recipient,
            amount,
            currentNonce
        );
        
        return messageId;
    }
    
    /**
     * @notice Receive WETH9 tokens from another chain via CCIP
     * @param message The CCIP message
     */
    function ccipReceive(
        IRouterClient.Any2EVMMessage calldata message
    ) external onlyRouter {
        // Replay protection: check if message already processed
        require(!processedTransfers[message.messageId], "CCIPWETH9Bridge: transfer already processed");
        
        // Mark as processed
        processedTransfers[message.messageId] = true;
        
        // Validate token amounts
        require(message.tokenAmounts.length > 0, "CCIPWETH9Bridge: no tokens");
        require(message.tokenAmounts[0].token == weth9, "CCIPWETH9Bridge: invalid token");
        
        uint256 amount = message.tokenAmounts[0].amount;
        require(amount > 0, "CCIPWETH9Bridge: invalid amount");
        
        // Decode transfer data (recipient, amount, sender, nonce)
        (address recipient, , , ) = abi.decode(
            message.data,
            (address, uint256, address, uint256)
        );
        
        require(recipient != address(0), "CCIPWETH9Bridge: zero recipient");
        
        // Transfer WETH9 to recipient
        require(IERC20(weth9).transfer(recipient, amount), "CCIPWETH9Bridge: transfer failed");
        
        emit CrossChainTransferCompleted(
            message.messageId,
            message.sourceChainSelector,
            recipient,
            amount
        );
    }
    
    /**
     * @notice Calculate fee for cross-chain transfer
     * @param destinationChainSelector The chain selector of the destination chain
     * @param amount The amount of WETH9 to send
     * @return fee The fee required for the transfer
     */
    function calculateFee(
        uint64 destinationChainSelector,
        uint256 amount
    ) external view returns (uint256 fee) {
        DestinationChain memory dest = destinations[destinationChainSelector];
        require(dest.enabled, "CCIPWETH9Bridge: destination not enabled");
        
        bytes memory data = abi.encode(address(0), amount, address(0), 0);
        
        IRouterClient.EVM2AnyMessage memory message = IRouterClient.EVM2AnyMessage({
            receiver: abi.encode(dest.receiverBridge),
            data: data,
            tokenAmounts: new IRouterClient.TokenAmount[](1),
            feeToken: feeToken,
            extraArgs: ""
        });
        
        message.tokenAmounts[0] = IRouterClient.TokenAmount({
            token: weth9,
            amount: amount,
            amountType: IRouterClient.TokenAmountType.Fiat
        });
        
        return ccipRouter.getFee(destinationChainSelector, message);
    }
    
    /**
     * @notice Add destination chain
     */
    function addDestination(
        uint64 chainSelector,
        address receiverBridge
    ) external onlyAdmin {
        require(receiverBridge != address(0), "CCIPWETH9Bridge: zero address");
        require(!destinations[chainSelector].enabled, "CCIPWETH9Bridge: destination already exists");
        
        destinations[chainSelector] = DestinationChain({
            chainSelector: chainSelector,
            receiverBridge: receiverBridge,
            enabled: true
        });
        destinationChains.push(chainSelector);
        
        emit DestinationAdded(chainSelector, receiverBridge);
    }
    
    /**
     * @notice Remove destination chain
     */
    function removeDestination(uint64 chainSelector) external onlyAdmin {
        require(destinations[chainSelector].enabled, "CCIPWETH9Bridge: destination not found");
        destinations[chainSelector].enabled = false;
        
        // Remove from array
        for (uint256 i = 0; i < destinationChains.length; i++) {
            if (destinationChains[i] == chainSelector) {
                destinationChains[i] = destinationChains[destinationChains.length - 1];
                destinationChains.pop();
                break;
            }
        }
        
        emit DestinationRemoved(chainSelector);
    }
    
    /**
     * @notice Update destination receiver bridge
     */
    function updateDestination(
        uint64 chainSelector,
        address receiverBridge
    ) external onlyAdmin {
        require(destinations[chainSelector].enabled, "CCIPWETH9Bridge: destination not found");
        require(receiverBridge != address(0), "CCIPWETH9Bridge: zero address");
        
        destinations[chainSelector].receiverBridge = receiverBridge;
        emit DestinationUpdated(chainSelector, receiverBridge);
    }
    
    /**
     * @notice Update fee token
     */
    function updateFeeToken(address newFeeToken) external onlyAdmin {
        require(newFeeToken != address(0), "CCIPWETH9Bridge: zero address");
        feeToken = newFeeToken;
    }
    
    /**
     * @notice Change admin
     */
    function changeAdmin(address newAdmin) external onlyAdmin {
        require(newAdmin != address(0), "CCIPWETH9Bridge: zero address");
        admin = newAdmin;
    }
    
    /**
     * @notice Get destination chains
     */
    function getDestinationChains() external view returns (uint64[] memory) {
        return destinationChains;
    }
    
    /**
     * @notice Get user nonce
     */
    function getUserNonce(address user) external view returns (uint256) {
        return nonces[user];
    }
}

