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

import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";

/**
 * @title BridgeRegistry
 * @notice Registry for bridge configuration: destinations, tokens, fees, and routing
 */
contract BridgeRegistry is AccessControl, Pausable {
    bytes32 public constant REGISTRAR_ROLE = keccak256("REGISTRAR_ROLE");

    struct Destination {
        uint256 chainId; // 0 for non-EVM
        string chainName;
        bool enabled;
        uint256 minFinalityBlocks;
        uint256 timeoutSeconds;
        uint256 baseFee; // Base fee in basis points (10000 = 100%)
        address feeRecipient;
    }

    struct TokenConfig {
        address tokenAddress;
        bool allowed;
        uint256 minAmount;
        uint256 maxAmount;
        uint256[] allowedDestinations; // Chain IDs or 0 for non-EVM
        uint8 riskLevel; // 0-255, higher = riskier
        uint256 bridgeFeeBps; // Bridge fee in basis points
    }

    struct RouteHealth {
        uint256 successCount;
        uint256 failureCount;
        uint256 lastUpdate;
        uint256 avgSettlementTime; // In seconds
    }

    mapping(uint256 => Destination) public destinations; // chainId -> Destination
    mapping(address => TokenConfig) public tokenConfigs;
    mapping(uint256 => mapping(address => RouteHealth)) public routeHealth; // chainId -> token -> health
    mapping(address => bool) public allowedTokens;

    uint256[] public destinationChainIds;
    address[] public registeredTokens;

    event DestinationRegistered(
        uint256 indexed chainId,
        string chainName,
        uint256 minFinalityBlocks,
        uint256 timeoutSeconds
    );

    event DestinationUpdated(uint256 indexed chainId, bool enabled);

    event TokenRegistered(
        address indexed token,
        uint256 minAmount,
        uint256 maxAmount,
        uint8 riskLevel
    );

    event TokenUpdated(address indexed token, bool allowed);

    event RouteHealthUpdated(
        uint256 indexed chainId,
        address indexed token,
        bool success,
        uint256 settlementTime
    );

    error DestinationNotFound();
    error TokenNotAllowed();
    error InvalidAmount();
    error InvalidDestination();
    error InvalidFee();

    constructor(address admin) {
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(REGISTRAR_ROLE, admin);
    }

    /**
     * @notice Register a new destination chain
     * @param chainId Chain ID (0 for non-EVM like XRPL)
     * @param chainName Human-readable chain name
     * @param minFinalityBlocks Minimum blocks/ledgers for finality
     * @param timeoutSeconds Timeout for refund eligibility
     * @param baseFee Base fee in basis points
     * @param feeRecipient Address to receive fees
     */
    function registerDestination(
        uint256 chainId,
        string calldata chainName,
        uint256 minFinalityBlocks,
        uint256 timeoutSeconds,
        uint256 baseFee,
        address feeRecipient
    ) external onlyRole(REGISTRAR_ROLE) {
        if (baseFee > 10000) revert InvalidFee(); // Max 100%

        destinations[chainId] = Destination({
            chainId: chainId,
            chainName: chainName,
            enabled: true,
            minFinalityBlocks: minFinalityBlocks,
            timeoutSeconds: timeoutSeconds,
            baseFee: baseFee,
            feeRecipient: feeRecipient
        });

        // Add to list if not already present
        bool exists = false;
        for (uint256 i = 0; i < destinationChainIds.length; i++) {
            if (destinationChainIds[i] == chainId) {
                exists = true;
                break;
            }
        }
        if (!exists) {
            destinationChainIds.push(chainId);
        }

        emit DestinationRegistered(chainId, chainName, minFinalityBlocks, timeoutSeconds);
    }

    /**
     * @notice Update destination enabled status
     * @param chainId Chain ID
     * @param enabled Enabled status
     */
    function updateDestination(
        uint256 chainId,
        bool enabled
    ) external onlyRole(REGISTRAR_ROLE) {
        if (destinations[chainId].chainId == 0 && chainId != 0) revert DestinationNotFound();
        destinations[chainId].enabled = enabled;
        emit DestinationUpdated(chainId, enabled);
    }

    /**
     * @notice Register a token for bridging
     * @param token Token address
     * @param minAmount Minimum bridge amount
     * @param maxAmount Maximum bridge amount
     * @param allowedDestinations Array of allowed destination chain IDs
     * @param riskLevel Risk level (0-255)
     * @param bridgeFeeBps Bridge fee in basis points
     */
    function registerToken(
        address token,
        uint256 minAmount,
        uint256 maxAmount,
        uint256[] calldata allowedDestinations,
        uint8 riskLevel,
        uint256 bridgeFeeBps
    ) external onlyRole(REGISTRAR_ROLE) {
        if (bridgeFeeBps > 10000) revert InvalidFee();

        tokenConfigs[token] = TokenConfig({
            tokenAddress: token,
            allowed: true,
            minAmount: minAmount,
            maxAmount: maxAmount,
            allowedDestinations: allowedDestinations,
            riskLevel: riskLevel,
            bridgeFeeBps: bridgeFeeBps
        });

        allowedTokens[token] = true;

        // Add to list if not already present
        bool exists = false;
        for (uint256 i = 0; i < registeredTokens.length; i++) {
            if (registeredTokens[i] == token) {
                exists = true;
                break;
            }
        }
        if (!exists) {
            registeredTokens.push(token);
        }

        emit TokenRegistered(token, minAmount, maxAmount, riskLevel);
    }

    /**
     * @notice Update token allowed status
     * @param token Token address
     * @param allowed Allowed status
     */
    function updateToken(address token, bool allowed) external onlyRole(REGISTRAR_ROLE) {
        if (tokenConfigs[token].tokenAddress == address(0)) revert TokenNotAllowed();
        tokenConfigs[token].allowed = allowed;
        allowedTokens[token] = allowed;
        emit TokenUpdated(token, allowed);
    }

    /**
     * @notice Update route health metrics
     * @param chainId Destination chain ID
     * @param token Token address
     * @param success Whether the route succeeded
     * @param settlementTime Settlement time in seconds
     */
    function updateRouteHealth(
        uint256 chainId,
        address token,
        bool success,
        uint256 settlementTime
    ) external onlyRole(REGISTRAR_ROLE) {
        RouteHealth storage health = routeHealth[chainId][token];

        if (success) {
            health.successCount++;
            // Update average settlement time (simple moving average)
            if (health.successCount == 1) {
                health.avgSettlementTime = settlementTime;
            } else {
                health.avgSettlementTime =
                    (health.avgSettlementTime * (health.successCount - 1) + settlementTime) /
                    health.successCount;
            }
        } else {
            health.failureCount++;
        }

        health.lastUpdate = block.timestamp;

        emit RouteHealthUpdated(chainId, token, success, settlementTime);
    }

    /**
     * @notice Validate bridge request
     * @param token Token address (address(0) for native)
     * @param amount Amount to bridge
     * @param destinationChainId Destination chain ID
     * @return isValid Whether request is valid
     * @return fee Fee amount
     */
    function validateBridgeRequest(
        address token,
        uint256 amount,
        uint256 destinationChainId
    ) external view returns (bool isValid, uint256 fee) {
        // Check destination exists and is enabled
        Destination memory dest = destinations[destinationChainId];
        if (dest.chainId == 0 && destinationChainId != 0) {
            return (false, 0);
        }
        if (!dest.enabled) {
            return (false, 0);
        }

        // For native ETH, allow if destination is enabled
        if (token == address(0)) {
            fee = (amount * dest.baseFee) / 10000;
            return (true, fee);
        }

        // Check token is registered and allowed
        TokenConfig memory config = tokenConfigs[token];
        if (!config.allowed || config.tokenAddress == address(0)) {
            return (false, 0);
        }

        // Check amount limits
        if (amount < config.minAmount || amount > config.maxAmount) {
            return (false, 0);
        }

        // Check destination is allowed for this token
        bool destAllowed = false;
        for (uint256 i = 0; i < config.allowedDestinations.length; i++) {
            if (config.allowedDestinations[i] == destinationChainId) {
                destAllowed = true;
                break;
            }
        }
        if (!destAllowed) {
            return (false, 0);
        }

        // Calculate fee (base fee + token-specific fee)
        uint256 baseFeeAmount = (amount * dest.baseFee) / 10000;
        uint256 tokenFeeAmount = (amount * config.bridgeFeeBps) / 10000;
        fee = baseFeeAmount + tokenFeeAmount;

        return (true, fee);
    }

    /**
     * @notice Get route health score (0-10000, higher is better)
     * @param chainId Destination chain ID
     * @param token Token address
     * @return score Health score
     */
    function getRouteHealthScore(
        uint256 chainId,
        address token
    ) external view returns (uint256 score) {
        RouteHealth memory health = routeHealth[chainId][token];
        uint256 total = health.successCount + health.failureCount;
        if (total == 0) return 5000; // Default 50% if no data

        score = (health.successCount * 10000) / total;
        return score;
    }

    /**
     * @notice Get all registered destinations
     * @return chainIds Array of chain IDs
     */
    function getAllDestinations() external view returns (uint256[] memory) {
        return destinationChainIds;
    }

    /**
     * @notice Get all registered tokens
     * @return tokens Array of token addresses
     */
    function getAllTokens() external view returns (address[] memory) {
        return registeredTokens;
    }

    /**
     * @notice Pause registry
     */
    function pause() external onlyRole(DEFAULT_ADMIN_ROLE) {
        _pause();
    }

    /**
     * @notice Unpause registry
     */
    function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) {
        _unpause();
    }
}
