// 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 Sender
 * @notice Chainlink CCIP sender for cross-chain oracle data transmission
 * @dev Sends oracle updates to other chains via CCIP
 */
contract CCIPSender {
    using SafeERC20 for IERC20;
    
    IRouterClient public immutable ccipRouter;
    address public oracleAggregator;
    address public admin;
    address public feeToken; // LINK token address
    
    // Destination chain configurations
    struct DestinationChain {
        uint64 chainSelector;
        address receiver;
        bool enabled;
    }
    
    mapping(uint64 => DestinationChain) public destinations;
    uint64[] public destinationChains;
    
    event MessageSent(
        bytes32 indexed messageId,
        uint64 indexed destinationChainSelector,
        address receiver,
        bytes data
    );
    event DestinationAdded(uint64 chainSelector, address receiver);
    event DestinationRemoved(uint64 chainSelector);
    event DestinationUpdated(uint64 chainSelector, address receiver);
    
    modifier onlyAdmin() {
        require(msg.sender == admin, "CCIPSender: only admin");
        _;
    }
    
    modifier onlyAggregator() {
        require(msg.sender == oracleAggregator, "CCIPSender: only aggregator");
        _;
    }
    
    constructor(address _ccipRouter, address _oracleAggregator, address _feeToken) {
        require(_ccipRouter != address(0), "CCIPSender: zero router");
        // Allow zero address for native token fees (ETH)
        // If feeToken is zero, fees are paid in native token (msg.value)
        ccipRouter = IRouterClient(_ccipRouter);
        oracleAggregator = _oracleAggregator;
        feeToken = _feeToken;
        admin = msg.sender;
    }
    
    /**
     * @notice Send oracle update to destination chain
     * @dev Implements full CCIP interface with fee payment
     */
    function sendOracleUpdate(
        uint64 destinationChainSelector,
        uint256 answer,
        uint256 roundId,
        uint256 timestamp
    ) external payable onlyAggregator returns (bytes32 messageId) {
        DestinationChain memory dest = destinations[destinationChainSelector];
        require(dest.enabled, "CCIPSender: destination not enabled");
        
        // Encode oracle data (answer, roundId, timestamp)
        bytes memory data = abi.encode(answer, roundId, timestamp);
        
        // Prepare CCIP message
        IRouterClient.EVM2AnyMessage memory message = IRouterClient.EVM2AnyMessage({
            receiver: abi.encode(dest.receiver),
            data: data,
            tokenAmounts: new IRouterClient.TokenAmount[](0),
            feeToken: feeToken,
            extraArgs: ""
        });
        
        // Calculate fee
        uint256 fee = ccipRouter.getFee(destinationChainSelector, message);
        
        // Approve and pay fee
        if (fee > 0) {
            if (feeToken == address(0)) {
                // Native token (ETH) fees - require msg.value
                require(msg.value >= fee, "CCIPSender: insufficient native token fee");
            } else {
                // ERC20 token fees
                IERC20(feeToken).safeTransferFrom(msg.sender, address(this), fee);
                // Use safeIncreaseAllowance instead of deprecated safeApprove
                SafeERC20.safeIncreaseAllowance(IERC20(feeToken), address(ccipRouter), fee);
            }
        }
        
        // Send via CCIP
        if (feeToken == address(0)) {
            // Native token fees - send with msg.value
            (messageId, ) = ccipRouter.ccipSend{value: fee}(destinationChainSelector, message);
        } else {
            // ERC20 token fees
            (messageId, ) = ccipRouter.ccipSend(destinationChainSelector, message);
        }
        
        emit MessageSent(messageId, destinationChainSelector, dest.receiver, data);
        return messageId;
    }
    
    /**
     * @notice Calculate fee for sending oracle update
     */
    function calculateFee(
        uint64 destinationChainSelector,
        bytes memory data
    ) external view returns (uint256) {
        DestinationChain memory dest = destinations[destinationChainSelector];
        require(dest.enabled, "CCIPSender: destination not enabled");
        
        IRouterClient.EVM2AnyMessage memory message = IRouterClient.EVM2AnyMessage({
            receiver: abi.encode(dest.receiver),
            data: data,
            tokenAmounts: new IRouterClient.TokenAmount[](0),
            feeToken: feeToken,
            extraArgs: ""
        });
        
        return ccipRouter.getFee(destinationChainSelector, message);
    }
    
    /**
     * @notice Add destination chain
     */
    function addDestination(
        uint64 chainSelector,
        address receiver
    ) external onlyAdmin {
        require(receiver != address(0), "CCIPSender: zero address");
        require(!destinations[chainSelector].enabled, "CCIPSender: destination already exists");
        
        destinations[chainSelector] = DestinationChain({
            chainSelector: chainSelector,
            receiver: receiver,
            enabled: true
        });
        destinationChains.push(chainSelector);
        
        emit DestinationAdded(chainSelector, receiver);
    }
    
    /**
     * @notice Remove destination chain
     */
    function removeDestination(uint64 chainSelector) external onlyAdmin {
        require(destinations[chainSelector].enabled, "CCIPSender: 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
     */
    function updateDestination(
        uint64 chainSelector,
        address receiver
    ) external onlyAdmin {
        require(destinations[chainSelector].enabled, "CCIPSender: destination not found");
        require(receiver != address(0), "CCIPSender: zero address");
        
        destinations[chainSelector].receiver = receiver;
        emit DestinationUpdated(chainSelector, receiver);
    }
    
    /**
     * @notice Update fee token
     * @dev Allows zero address for native token fees (ETH)
     */
    function updateFeeToken(address newFeeToken) external onlyAdmin {
        // Allow zero address for native token fees
        feeToken = newFeeToken;
    }
    
    /**
     * @notice Update oracle aggregator
     */
    function updateOracleAggregator(address newAggregator) external onlyAdmin {
        require(newAggregator != address(0), "CCIPSender: zero address");
        oracleAggregator = newAggregator;
    }
    
    /**
     * @notice Change admin
     */
    function changeAdmin(address newAdmin) external onlyAdmin {
        require(newAdmin != address(0), "CCIPSender: zero address");
        admin = newAdmin;
    }
    
    /**
     * @notice Get destination chains
     */
    function getDestinationChains() external view returns (uint64[] memory) {
        return destinationChains;
    }
}

