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

import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "../vendor/openzeppelin/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "../vendor/openzeppelin/ReentrancyGuardUpgradeable.sol";
import "../registry/UniversalAssetRegistry.sol";
import "../ccip/IRouterClient.sol";

/**
 * @title UniversalCCIPBridge
 * @notice Main bridge contract supporting all asset types via CCIP
 * @dev Extends CCIP infrastructure with dynamic asset routing and PMM integration
 */
contract UniversalCCIPBridge is 
    Initializable,
    AccessControlUpgradeable,
    ReentrancyGuardUpgradeable,
    UUPSUpgradeable
{
    using SafeERC20 for IERC20;

    bytes32 public constant BRIDGE_OPERATOR_ROLE = keccak256("BRIDGE_OPERATOR_ROLE");
    bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE");

    struct BridgeOperation {
        address token;
        uint256 amount;
        uint64 destinationChain;
        address recipient;
        bytes32 assetType;
        bool usePMM;
        bool useVault;
        bytes complianceProof;
        bytes vaultInstructions;
    }

    struct Destination {
        address receiverBridge;
        bool enabled;
        uint256 addedAt;
    }

    // Core dependencies
    UniversalAssetRegistry public assetRegistry;
    IRouterClient public ccipRouter;
    address public liquidityManager;
    address public vaultFactory;
    
    // State
    mapping(address => mapping(uint64 => Destination)) public destinations;
    mapping(address => address) public userVaults;
    mapping(bytes32 => bool) public processedMessages;
    mapping(address => uint256) public nonces;
    
    // Events
    event BridgeExecuted(
        bytes32 indexed messageId,
        address indexed token,
        address indexed sender,
        uint256 amount,
        uint64 destinationChain,
        address recipient,
        bool usedPMM
    );
    
    event DestinationAdded(
        address indexed token,
        uint64 indexed chainSelector,
        address receiverBridge
    );
    
    event DestinationRemoved(
        address indexed token,
        uint64 indexed chainSelector
    );
    
    event MessageReceived(
        bytes32 indexed messageId,
        uint64 indexed sourceChainSelector,
        address sender,
        address token,
        uint256 amount
    );

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    function initialize(
        address _assetRegistry,
        address _ccipRouter,
        address admin
    ) external initializer {
        __AccessControl_init();
        __ReentrancyGuard_init();
        __UUPSUpgradeable_init();
        
        require(_assetRegistry != address(0), "Zero registry");
        
        assetRegistry = UniversalAssetRegistry(_assetRegistry);
        if (_ccipRouter != address(0)) {
            ccipRouter = IRouterClient(_ccipRouter);
        }
        // If _ccipRouter is zero, set via setCCIPRouter() after deployment (enables same initData for deterministic proxy address)
        
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(BRIDGE_OPERATOR_ROLE, admin);
        _grantRole(UPGRADER_ROLE, admin);
    }

    function _authorizeUpgrade(address newImplementation)
        internal override onlyRole(UPGRADER_ROLE) {}

    /**
     * @notice Set CCIP router (for deterministic deployment: initialize with router=0, then set per chain)
     */
    function setCCIPRouter(address _ccipRouter) external onlyRole(DEFAULT_ADMIN_ROLE) {
        require(_ccipRouter != address(0), "Zero router");
        ccipRouter = IRouterClient(_ccipRouter);
    }

    /**
     * @notice Main bridge function with asset type routing
     */
    function bridge(
        BridgeOperation calldata op
    ) external payable nonReentrant returns (bytes32 messageId) {
        // Validate asset is registered and active
        UniversalAssetRegistry.UniversalAsset memory asset = assetRegistry.getAsset(op.token);
        require(asset.isActive, "Asset not active");
        require(asset.tokenAddress != address(0), "Asset not registered");
        
        // Verify destination is enabled
        Destination memory dest = destinations[op.token][op.destinationChain];
        require(dest.enabled, "Destination not enabled");
        require(dest.receiverBridge != address(0), "Invalid receiver");
        
        // Validate amounts
        require(op.amount > 0, "Invalid amount");
        require(op.amount >= asset.minBridgeAmount, "Below minimum");
        require(op.amount <= asset.maxBridgeAmount, "Above maximum");
        
        // Transfer tokens from user
        IERC20(op.token).safeTransferFrom(msg.sender, address(this), op.amount);
        
        // Execute bridge with optional PMM
        if (op.usePMM && liquidityManager != address(0)) {
            _executeBridgeWithPMM(op);
        }
        
        // Execute bridge with optional vault
        if (op.useVault && vaultFactory != address(0)) {
            _executeBridgeWithVault(op);
        }
        
        // Send CCIP message
        messageId = _sendCCIPMessage(op, dest);
        
        // Increment nonce
        nonces[msg.sender]++;
        
        emit BridgeExecuted(
            messageId,
            op.token,
            msg.sender,
            op.amount,
            op.destinationChain,
            op.recipient,
            op.usePMM
        );
        
        return messageId;
    }

    /**
     * @notice Execute bridge with PMM liquidity
     */
    function _executeBridgeWithPMM(BridgeOperation calldata op) internal {
        if (liquidityManager == address(0)) return;
        
        // Call liquidity manager to provide liquidity
        (bool success, ) = liquidityManager.call(
            abi.encodeWithSignature(
                "provideLiquidity(address,uint256,bytes)",
                op.token,
                op.amount,
                ""
            )
        );
        
        // PMM is optional, don't revert if it fails
        if (!success) {
            // Log or handle PMM failure gracefully
        }
    }

    /**
     * @notice Execute bridge with vault
     */
    function _executeBridgeWithVault(BridgeOperation calldata op) internal {
        if (vaultFactory == address(0)) return;
        
        // Get or create vault for user
        address vault = userVaults[msg.sender];
        if (vault == address(0)) {
            // Call vault factory to create vault
            (bool success, bytes memory data) = vaultFactory.call(
                abi.encodeWithSignature("createVault(address)", msg.sender)
            );
            if (success) {
                vault = abi.decode(data, (address));
                userVaults[msg.sender] = vault;
            }
        }
        
        // If vault exists, record operation
        if (vault != address(0)) {
            // Call vault to record bridge operation
            (bool ok, ) = vault.call(
                abi.encodeWithSignature(
                    "recordBridgeOperation(bytes32,address,uint256,uint64)",
                    bytes32(0), // messageId will be set after CCIP send
                    op.token,
                    op.amount,
                    op.destinationChain
                )
            );
            if (!ok) {
                // Vault recording is optional; ignore failure
            }
        }
    }

    /**
     * @notice Send CCIP message
     */
    function _sendCCIPMessage(
        BridgeOperation calldata op,
        Destination memory dest
    ) internal returns (bytes32 messageId) {
        // Encode message data
        bytes memory data = abi.encode(
            op.recipient,
            op.amount,
            msg.sender,
            nonces[msg.sender]
        );
        
        // Prepare CCIP message
        IRouterClient.EVM2AnyMessage memory message = IRouterClient.EVM2AnyMessage({
            receiver: abi.encode(dest.receiverBridge),
            data: data,
            tokenAmounts: new IRouterClient.TokenAmount[](1),
            feeToken: address(0), // Pay in native
            extraArgs: ""
        });
        
        // Set token amount
        message.tokenAmounts[0] = IRouterClient.TokenAmount({
            token: op.token,
            amount: op.amount,
            amountType: IRouterClient.TokenAmountType.Fiat
        });
        
        // Calculate fee
        uint256 fee = ccipRouter.getFee(op.destinationChain, message);
        require(address(this).balance >= fee, "Insufficient fee");
        
        // Send via CCIP
        (messageId, ) = ccipRouter.ccipSend{value: fee}(op.destinationChain, message);
        
        return messageId;
    }

    /**
     * @notice Add destination for token
     */
    function addDestination(
        address token,
        uint64 chainSelector,
        address receiverBridge
    ) external onlyRole(BRIDGE_OPERATOR_ROLE) {
        require(token != address(0), "Zero token");
        require(receiverBridge != address(0), "Zero receiver");
        
        destinations[token][chainSelector] = Destination({
            receiverBridge: receiverBridge,
            enabled: true,
            addedAt: block.timestamp
        });
        
        emit DestinationAdded(token, chainSelector, receiverBridge);
    }

    /**
     * @notice Remove destination
     */
    function removeDestination(
        address token,
        uint64 chainSelector
    ) external onlyRole(BRIDGE_OPERATOR_ROLE) {
        destinations[token][chainSelector].enabled = false;
        
        emit DestinationRemoved(token, chainSelector);
    }

    /**
     * @notice Set liquidity manager
     */
    function setLiquidityManager(address _liquidityManager) external onlyRole(DEFAULT_ADMIN_ROLE) {
        liquidityManager = _liquidityManager;
    }

    /**
     * @notice Set vault factory
     */
    function setVaultFactory(address _vaultFactory) external onlyRole(DEFAULT_ADMIN_ROLE) {
        vaultFactory = _vaultFactory;
    }

    /**
     * @notice Receive native tokens
     */
    receive() external payable {}

    /**
     * @notice Withdraw native tokens
     */
    function withdraw() external onlyRole(DEFAULT_ADMIN_ROLE) {
        payable(msg.sender).transfer(address(this).balance);
    }

    // View functions
    
    function getDestination(address token, uint64 chainSelector)
        external view returns (Destination memory) {
        return destinations[token][chainSelector];
    }

    function getUserVault(address user) external view returns (address) {
        return userVaults[user];
    }

    function getUserNonce(address user) external view returns (uint256) {
        return nonces[user];
    }
}
