// 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";

/**
 * @title ChainRegistry
 * @notice Central registry for all supported blockchains (EVM and non-EVM)
 * @dev Maps chain identifiers to adapter contracts and metadata
 */
contract ChainRegistry is 
    Initializable,
    AccessControlUpgradeable,
    UUPSUpgradeable
{
    bytes32 public constant REGISTRY_ADMIN_ROLE = keccak256("REGISTRY_ADMIN_ROLE");
    bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE");

    enum ChainType { 
        EVM,           // Ethereum, Polygon, Arbitrum, etc.
        XRPL,          // XRP Ledger
        XDC,           // XDC Network (EVM-compatible but different address format)
        Stellar,       // Stellar Network
        Algorand,      // Algorand
        Hedera,        // Hedera Hashgraph
        Tron,          // Tron
        TON,           // The Open Network
        Cosmos,        // Cosmos Hub
        Solana,        // Solana
        Fabric,        // Hyperledger Fabric
        Corda,         // R3 Corda
        Indy,          // Hyperledger Indy
        Firefly,       // Hyperledger Firefly
        Cacti,         // Hyperledger Cacti
        Other          // Custom/unknown
    }

    struct ChainMetadata {
        uint256 chainId;              // For EVM chains (0 for non-EVM)
        string chainIdentifier;      // For non-EVM (e.g., "XRPL", "Fabric-Channel1")
        ChainType chainType;
        address adapter;             // Bridge adapter contract address
        bool isActive;
        uint256 minConfirmations;    // Required confirmations
        uint256 avgBlockTime;         // Average block time in seconds
        bool requiresOracle;         // Non-EVM chains need oracle
        string rpcEndpoint;           // RPC endpoint (encrypted or public)
        string explorerUrl;           // Block explorer URL
        bytes additionalData;         // Chain-specific config
        uint256 addedAt;
        uint256 lastUpdated;
    }

    // EVM chains: chainId => metadata
    mapping(uint256 => ChainMetadata) public evmChains;
    
    // Non-EVM chains: identifier => metadata
    mapping(string => ChainMetadata) public nonEvmChains;
    
    // All registered chain identifiers
    uint256[] public registeredEVMChainIds;
    string[] public registeredNonEVMIdentifiers;
    
    // Adapter validation
    mapping(address => bool) public isValidAdapter;
    mapping(address => ChainType) public adapterToChainType;

    event ChainRegistered(
        uint256 indexed chainId,
        string indexed chainIdentifier,
        ChainType chainType,
        address adapter
    );
    
    event ChainUpdated(
        uint256 indexed chainId,
        string indexed chainIdentifier,
        bool isActive
    );
    
    event ChainDeactivated(
        uint256 indexed chainId,
        string indexed chainIdentifier
    );
    
    event AdapterUpdated(
        uint256 indexed chainId,
        string indexed chainIdentifier,
        address oldAdapter,
        address newAdapter
    );

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

    function initialize(address admin) external initializer {
        __AccessControl_init();
        __UUPSUpgradeable_init();
        
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(REGISTRY_ADMIN_ROLE, admin);
        _grantRole(UPGRADER_ROLE, admin);
    }

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

    /**
     * @notice Register an EVM chain
     */
    function registerEVMChain(
        uint256 chainId,
        address adapter,
        string calldata explorerUrl,
        uint256 minConfirmations,
        uint256 avgBlockTime,
        bytes calldata additionalData
    ) external onlyRole(REGISTRY_ADMIN_ROLE) {
        require(chainId != 0, "Invalid chain ID");
        require(adapter != address(0), "Zero adapter");
        require(adapter.code.length > 0, "Not a contract");
        
        ChainMetadata storage chain = evmChains[chainId];
        
        // If new chain, add to list
        if (chain.addedAt == 0) {
            registeredEVMChainIds.push(chainId);
        }
        
        chain.chainId = chainId;
        chain.chainIdentifier = string(abi.encodePacked("EVM-", chainId));
        chain.chainType = ChainType.EVM;
        chain.adapter = adapter;
        chain.isActive = true;
        chain.minConfirmations = minConfirmations;
        chain.avgBlockTime = avgBlockTime;
        chain.requiresOracle = false;
        chain.explorerUrl = explorerUrl;
        chain.additionalData = additionalData;
        chain.lastUpdated = block.timestamp;
        
        if (chain.addedAt == 0) {
            chain.addedAt = block.timestamp;
        }
        
        isValidAdapter[adapter] = true;
        adapterToChainType[adapter] = ChainType.EVM;
        
        emit ChainRegistered(chainId, chain.chainIdentifier, ChainType.EVM, adapter);
    }

    /**
     * @notice Register a non-EVM chain
     */
    function registerNonEVMChain(
        string calldata chainIdentifier,
        ChainType chainType,
        address adapter,
        string calldata explorerUrl,
        uint256 minConfirmations,
        uint256 avgBlockTime,
        bool requiresOracle,
        bytes calldata additionalData
    ) external onlyRole(REGISTRY_ADMIN_ROLE) {
        require(bytes(chainIdentifier).length > 0, "Empty identifier");
        require(adapter != address(0), "Zero adapter");
        require(adapter.code.length > 0, "Not a contract");
        require(chainType != ChainType.EVM, "Use registerEVMChain");
        
        ChainMetadata storage chain = nonEvmChains[chainIdentifier];
        
        // If new chain, add to list
        if (chain.addedAt == 0) {
            registeredNonEVMIdentifiers.push(chainIdentifier);
        }
        
        chain.chainId = 0;
        chain.chainIdentifier = chainIdentifier;
        chain.chainType = chainType;
        chain.adapter = adapter;
        chain.isActive = true;
        chain.minConfirmations = minConfirmations;
        chain.avgBlockTime = avgBlockTime;
        chain.requiresOracle = requiresOracle;
        chain.explorerUrl = explorerUrl;
        chain.additionalData = additionalData;
        chain.lastUpdated = block.timestamp;
        
        if (chain.addedAt == 0) {
            chain.addedAt = block.timestamp;
        }
        
        isValidAdapter[adapter] = true;
        adapterToChainType[adapter] = chainType;
        
        emit ChainRegistered(0, chainIdentifier, chainType, adapter);
    }

    /**
     * @notice Update chain adapter
     */
    function updateAdapter(
        uint256 chainId,
        string calldata chainIdentifier,
        address newAdapter
    ) external onlyRole(REGISTRY_ADMIN_ROLE) {
        require(newAdapter != address(0), "Zero adapter");
        require(newAdapter.code.length > 0, "Not a contract");
        
        address oldAdapter;
        
        if (chainId != 0) {
            ChainMetadata storage chain = evmChains[chainId];
            require(chain.addedAt != 0, "Chain not registered");
            oldAdapter = chain.adapter;
            chain.adapter = newAdapter;
            chain.lastUpdated = block.timestamp;
        } else {
            ChainMetadata storage chain = nonEvmChains[chainIdentifier];
            require(chain.addedAt != 0, "Chain not registered");
            oldAdapter = chain.adapter;
            chain.adapter = newAdapter;
            chain.lastUpdated = block.timestamp;
        }
        
        isValidAdapter[oldAdapter] = false;
        isValidAdapter[newAdapter] = true;
        
        emit AdapterUpdated(chainId, chainIdentifier, oldAdapter, newAdapter);
    }

    /**
     * @notice Enable/disable chain
     */
    function setChainActive(
        uint256 chainId,
        string calldata chainIdentifier,
        bool active
    ) external onlyRole(REGISTRY_ADMIN_ROLE) {
        if (chainId != 0) {
            ChainMetadata storage chain = evmChains[chainId];
            require(chain.addedAt != 0, "Chain not registered");
            chain.isActive = active;
            chain.lastUpdated = block.timestamp;
        } else {
            ChainMetadata storage chain = nonEvmChains[chainIdentifier];
            require(chain.addedAt != 0, "Chain not registered");
            chain.isActive = active;
            chain.lastUpdated = block.timestamp;
        }
        
        emit ChainUpdated(chainId, chainIdentifier, active);
        
        if (!active) {
            emit ChainDeactivated(chainId, chainIdentifier);
        }
    }

    // View functions

    function getEVMChain(uint256 chainId) 
        external view returns (ChainMetadata memory) {
        return evmChains[chainId];
    }

    function getNonEVMChain(string calldata chainIdentifier)
        external view returns (ChainMetadata memory) {
        return nonEvmChains[chainIdentifier];
    }

    function getAdapter(uint256 chainId, string calldata chainIdentifier)
        external view returns (address) {
        if (chainId != 0) {
            return evmChains[chainId].adapter;
        } else {
            return nonEvmChains[chainIdentifier].adapter;
        }
    }

    function isChainActive(uint256 chainId, string calldata chainIdentifier)
        external view returns (bool) {
        if (chainId != 0) {
            return evmChains[chainId].isActive;
        } else {
            return nonEvmChains[chainIdentifier].isActive;
        }
    }

    function getAllEVMChains() 
        external view returns (uint256[] memory, ChainMetadata[] memory) {
        uint256 length = registeredEVMChainIds.length;
        ChainMetadata[] memory chains = new ChainMetadata[](length);
        
        for (uint256 i = 0; i < length; i++) {
            chains[i] = evmChains[registeredEVMChainIds[i]];
        }
        
        return (registeredEVMChainIds, chains);
    }

    function getAllNonEVMChains()
        external view returns (string[] memory, ChainMetadata[] memory) {
        uint256 length = registeredNonEVMIdentifiers.length;
        ChainMetadata[] memory chains = new ChainMetadata[](length);
        
        for (uint256 i = 0; i < length; i++) {
            chains[i] = nonEvmChains[registeredNonEVMIdentifiers[i]];
        }
        
        return (registeredNonEVMIdentifiers, chains);
    }

    function getTotalChains() external view returns (uint256, uint256) {
        return (registeredEVMChainIds.length, registeredNonEVMIdentifiers.length);
    }
}
