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

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

/**
 * @title TokenRegistry
 * @notice Registry for all tokenized assets with metadata
 */
contract TokenRegistry is AccessControl, Pausable {
    bytes32 public constant REGISTRAR_ROLE = keccak256("REGISTRAR_ROLE");

    struct TokenMetadata {
        address tokenAddress;
        string tokenId; // Fabric token ID
        string underlyingAsset; // EUR, USD, etc.
        address issuer;
        string backingReserve; // Reserve ID or address
        uint256 totalSupply;
        uint256 backedAmount; // Amount backed by reserves
        TokenStatus status;
        uint256 createdAt;
        uint256 updatedAt;
    }

    enum TokenStatus {
        PENDING,
        ACTIVE,
        SUSPENDED,
        REDEEMED
    }

    mapping(address => TokenMetadata) public tokens;
    mapping(string => address) public tokenIdToAddress; // Fabric tokenId -> Besu address
    address[] public registeredTokens;

    event TokenRegistered(
        address indexed tokenAddress,
        string indexed tokenId,
        string underlyingAsset,
        address indexed issuer
    );

    event TokenUpdated(
        address indexed tokenAddress,
        TokenStatus oldStatus,
        TokenStatus newStatus
    );

    event TokenSuspended(address indexed tokenAddress, string reason);
    event TokenActivated(address indexed tokenAddress);

    error TokenNotFound();
    error TokenAlreadyRegistered();
    error InvalidStatus();
    error InvalidBacking();

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

    /**
     * @notice Register a tokenized asset
     * @param tokenAddress ERC-20 token address
     * @param tokenId Fabric token ID
     * @param underlyingAsset Underlying asset type (EUR, USD, etc.)
     * @param issuer Issuer address
     * @param backingReserve Reserve identifier
     */
    function registerToken(
        address tokenAddress,
        string calldata tokenId,
        string calldata underlyingAsset,
        address issuer,
        string calldata backingReserve
    ) external onlyRole(REGISTRAR_ROLE) {
        if (tokens[tokenAddress].tokenAddress != address(0)) {
            revert TokenAlreadyRegistered();
        }

        tokens[tokenAddress] = TokenMetadata({
            tokenAddress: tokenAddress,
            tokenId: tokenId,
            underlyingAsset: underlyingAsset,
            issuer: issuer,
            backingReserve: backingReserve,
            totalSupply: 0,
            backedAmount: 0,
            status: TokenStatus.PENDING,
            createdAt: block.timestamp,
            updatedAt: block.timestamp
        });

        tokenIdToAddress[tokenId] = tokenAddress;
        registeredTokens.push(tokenAddress);

        emit TokenRegistered(tokenAddress, tokenId, underlyingAsset, issuer);
    }

    /**
     * @notice Update token status
     * @param tokenAddress Token address
     * @param newStatus New status
     */
    function updateTokenStatus(
        address tokenAddress,
        TokenStatus newStatus
    ) external onlyRole(REGISTRAR_ROLE) {
        TokenMetadata storage token = tokens[tokenAddress];
        if (token.tokenAddress == address(0)) revert TokenNotFound();

        TokenStatus oldStatus = token.status;
        token.status = newStatus;
        token.updatedAt = block.timestamp;

        emit TokenUpdated(tokenAddress, oldStatus, newStatus);
    }

    /**
     * @notice Update token supply and backing
     * @param tokenAddress Token address
     * @param totalSupply Current total supply
     * @param backedAmount Amount backed by reserves
     */
    function updateTokenBacking(
        address tokenAddress,
        uint256 totalSupply,
        uint256 backedAmount
    ) external onlyRole(REGISTRAR_ROLE) {
        TokenMetadata storage token = tokens[tokenAddress];
        if (token.tokenAddress == address(0)) revert TokenNotFound();

        // Verify 1:1 backing
        if (totalSupply > backedAmount) {
            revert InvalidBacking();
        }

        token.totalSupply = totalSupply;
        token.backedAmount = backedAmount;
        token.updatedAt = block.timestamp;
    }

    /**
     * @notice Suspend a token
     * @param tokenAddress Token address
     * @param reason Reason for suspension
     */
    function suspendToken(
        address tokenAddress,
        string calldata reason
    ) external onlyRole(REGISTRAR_ROLE) {
        TokenMetadata storage token = tokens[tokenAddress];
        if (token.tokenAddress == address(0)) revert TokenNotFound();

        token.status = TokenStatus.SUSPENDED;
        token.updatedAt = block.timestamp;

        emit TokenSuspended(tokenAddress, reason);
    }

    /**
     * @notice Activate a suspended token
     * @param tokenAddress Token address
     */
    function activateToken(address tokenAddress) external onlyRole(REGISTRAR_ROLE) {
        TokenMetadata storage token = tokens[tokenAddress];
        if (token.tokenAddress == address(0)) revert TokenNotFound();

        token.status = TokenStatus.ACTIVE;
        token.updatedAt = block.timestamp;

        emit TokenActivated(tokenAddress);
    }

    /**
     * @notice Get token metadata
     * @param tokenAddress Token address
     * @return Token metadata
     */
    function getToken(address tokenAddress) external view returns (TokenMetadata memory) {
        return tokens[tokenAddress];
    }

    /**
     * @notice Get token address by Fabric token ID
     * @param tokenId Fabric token ID
     * @return Token address
     */
    function getTokenByFabricId(string calldata tokenId) external view returns (address) {
        return tokenIdToAddress[tokenId];
    }

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

    /**
     * @notice Check if token is active
     * @param tokenAddress Token address
     * @return True if active
     */
    function isTokenActive(address tokenAddress) external view returns (bool) {
        TokenMetadata memory token = tokens[tokenAddress];
        return token.status == TokenStatus.ACTIVE;
    }

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

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