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

import "./IRouterClient.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

/**
 * @title CCIP Router Implementation
 * @notice Full Chainlink CCIP Router interface implementation
 * @dev Implements message sending, fee calculation, and message validation
 */
contract CCIPRouter is IRouterClient {
    using SafeERC20 for IERC20;

    // Fee token (LINK token address)
    address public immutable feeToken;
    
    // Message tracking
    mapping(bytes32 => bool) public sentMessages;
    mapping(bytes32 => bool) public receivedMessages;
    
    // Chain selectors
    mapping(uint64 => bool) public supportedChains;
    mapping(uint64 => address[]) public supportedTokens;
    
    // Fee configuration
    uint256 public baseFee; // Base fee in feeToken units
    uint256 public dataFeePerByte; // Fee per byte of data
    
    address public admin;
    
    // Events are inherited from IRouterClient interface
    
    modifier onlyAdmin() {
        require(msg.sender == admin, "CCIPRouter: only admin");
        _;
    }
    
    constructor(address _feeToken, uint256 _baseFee, uint256 _dataFeePerByte) {
        // Allow zero address for native token fees (ETH)
        // If feeToken is zero, fees are paid in native token (msg.value)
        feeToken = _feeToken;
        baseFee = _baseFee;
        dataFeePerByte = _dataFeePerByte;
        admin = msg.sender;
    }
    
    /**
     * @notice Send 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
     */
    function ccipSend(
        uint64 destinationChainSelector,
        EVM2AnyMessage memory message
    ) external payable returns (bytes32 messageId, uint256 fees) {
        require(supportedChains[destinationChainSelector], "CCIPRouter: chain not supported");
        require(message.receiver.length > 0, "CCIPRouter: empty receiver");
        
        // Calculate fee
        fees = getFee(destinationChainSelector, message);
        
        // Collect fee
        if (fees > 0) {
            if (feeToken == address(0)) {
                // Native token (ETH) fees
                require(msg.value >= fees, "CCIPRouter: insufficient native token fee");
            } else {
                // ERC20 token fees
                IERC20(feeToken).safeTransferFrom(msg.sender, address(this), fees);
            }
        }
        
        // Generate message ID
        messageId = keccak256(abi.encodePacked(
            block.chainid,
            destinationChainSelector,
            msg.sender,
            message.receiver,
            message.data,
            block.timestamp,
            block.number
        ));
        
        require(!sentMessages[messageId], "CCIPRouter: duplicate message");
        sentMessages[messageId] = true;
        
        emit MessageSent(
            messageId,
            destinationChainSelector,
            msg.sender,
            message.receiver,
            message.data,
            message.tokenAmounts,
            message.feeToken,
            message.extraArgs
        );
        
        return (messageId, fees);
    }
    
    /**
     * @notice Get 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
    ) public view returns (uint256 fee) {
        require(supportedChains[destinationChainSelector], "CCIPRouter: chain not supported");
        
        // Base fee
        fee = baseFee;
        
        // Data fee (per byte)
        fee += message.data.length * dataFeePerByte;
        
        // Token transfer fees
        for (uint256 i = 0; i < message.tokenAmounts.length; i++) {
            fee += message.tokenAmounts[i].amount / 1000; // 0.1% of token amount
        }
        
        return fee;
    }
    
    /**
     * @notice Get 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) {
        return supportedTokens[destinationChainSelector];
    }
    
    /**
     * @notice Add supported chain
     */
    function addSupportedChain(uint64 chainSelector) external onlyAdmin {
        supportedChains[chainSelector] = true;
    }
    
    /**
     * @notice Remove supported chain
     */
    function removeSupportedChain(uint64 chainSelector) external onlyAdmin {
        supportedChains[chainSelector] = false;
    }
    
    /**
     * @notice Add supported token for a chain
     */
    function addSupportedToken(uint64 chainSelector, address token) external onlyAdmin {
        require(token != address(0), "CCIPRouter: zero token");
        address[] storage tokens = supportedTokens[chainSelector];
        for (uint256 i = 0; i < tokens.length; i++) {
            require(tokens[i] != token, "CCIPRouter: token already supported");
        }
        tokens.push(token);
    }
    
    /**
     * @notice Update fee configuration
     */
    function updateFees(uint256 _baseFee, uint256 _dataFeePerByte) external onlyAdmin {
        baseFee = _baseFee;
        dataFeePerByte = _dataFeePerByte;
    }
    
    /**
     * @notice Change admin
     */
    function changeAdmin(address newAdmin) external onlyAdmin {
        require(newAdmin != address(0), "CCIPRouter: zero address");
        admin = newAdmin;
    }
    
    /**
     * @notice Withdraw collected fees
     */
    function withdrawFees(uint256 amount) external onlyAdmin {
        if (feeToken == address(0)) {
            // Native token (ETH) fees
            payable(admin).transfer(amount);
        } else {
            // ERC20 token fees
            IERC20(feeToken).safeTransfer(admin, amount);
        }
    }
    
    /**
     * @notice Withdraw all native token (ETH) fees
     */
    function withdrawNativeFees() external onlyAdmin {
        require(feeToken == address(0), "CCIPRouter: not native token");
        payable(admin).transfer(address(this).balance);
    }
    
    /**
     * @notice Receive native token (ETH)
     */
    receive() external payable {}
}
