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

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";
import "../bridge/interop/BridgeEscrowVault.sol";

/**
 * @title TokenizedEUR
 * @notice ERC-20 tokenized EUR backed 1:1 by reserves on Fabric
 * @dev Mintable/burnable by Fabric attestation via authorized minter
 */
contract TokenizedEUR is ERC20, ERC20Burnable, AccessControl, Pausable {
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
    bytes32 public constant ATTESTOR_ROLE = keccak256("ATTESTOR_ROLE");

    uint8 private constant DECIMALS = 18;

    struct FabricAttestation {
        bytes32 fabricTxHash;
        string tokenId;
        uint256 amount;
        address minter;
        uint256 timestamp;
        bytes signature;
    }

    mapping(bytes32 => bool) public processedFabricTxs;
    mapping(string => uint256) public fabricTokenBalances; // Fabric tokenId -> Besu balance

    event TokenizedEURMinted(
        address indexed to,
        uint256 amount,
        string indexed fabricTokenId,
        bytes32 fabricTxHash
    );

    event TokenizedEURBurned(
        address indexed from,
        uint256 amount,
        string indexed fabricTokenId,
        bytes32 fabricTxHash
    );

    event FabricAttestationReceived(
        bytes32 indexed fabricTxHash,
        string tokenId,
        uint256 amount
    );

    error ZeroAmount();
    error ZeroAddress();
    error InvalidFabricAttestation();
    error FabricTxAlreadyProcessed();
    error InsufficientFabricBalance();

    constructor(address admin) ERC20("Tokenized EUR", "EUR-T") {
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(MINTER_ROLE, admin);
        _grantRole(BURNER_ROLE, admin);
        _grantRole(ATTESTOR_ROLE, admin);
    }

    /**
     * @notice Mint tokenized EUR based on Fabric attestation
     * @param to Recipient address
     * @param amount Amount to mint
     * @param fabricTokenId Fabric token ID
     * @param fabricTxHash Fabric transaction hash
     * @param attestation Attestation from Fabric
     */
    function mintFromFabric(
        address to,
        uint256 amount,
        string memory fabricTokenId,
        bytes32 fabricTxHash,
        FabricAttestation calldata attestation
    ) external onlyRole(MINTER_ROLE) whenNotPaused {
        if (to == address(0)) revert ZeroAddress();
        if (amount == 0) revert ZeroAmount();
        if (processedFabricTxs[fabricTxHash]) revert FabricTxAlreadyProcessed();

        // Verify attestation (in production, verify signature)
        if (attestation.fabricTxHash != fabricTxHash) {
            revert InvalidFabricAttestation();
        }
        if (attestation.amount != amount) {
            revert InvalidFabricAttestation();
        }

        // Mark Fabric tx as processed
        processedFabricTxs[fabricTxHash] = true;

        // Update Fabric token balance mapping
        fabricTokenBalances[fabricTokenId] += amount;

        // Mint tokens
        _mint(to, amount);

        emit TokenizedEURMinted(to, amount, fabricTokenId, fabricTxHash);
        emit FabricAttestationReceived(fabricTxHash, fabricTokenId, amount);
    }

    /**
     * @notice Burn tokenized EUR to redeem on Fabric
     * @param from Address to burn from
     * @param amount Amount to burn
     * @param fabricTokenId Fabric token ID
     * @param fabricTxHash Fabric redemption transaction hash
     */
    function burnForFabric(
        address from,
        uint256 amount,
        string memory fabricTokenId,
        bytes32 fabricTxHash
    ) external onlyRole(BURNER_ROLE) whenNotPaused {
        if (from == address(0)) revert ZeroAddress();
        if (amount == 0) revert ZeroAmount();
        if (processedFabricTxs[fabricTxHash]) revert FabricTxAlreadyProcessed();

        // Check Fabric token balance
        if (fabricTokenBalances[fabricTokenId] < amount) {
            revert InsufficientFabricBalance();
        }

        // Mark Fabric tx as processed
        processedFabricTxs[fabricTxHash] = true;

        // Update Fabric token balance mapping
        fabricTokenBalances[fabricTokenId] -= amount;

        // Burn tokens
        _burn(from, amount);

        emit TokenizedEURBurned(from, amount, fabricTokenId, fabricTxHash);
    }

    /**
     * @notice Get Fabric token balance on Besu
     * @param fabricTokenId Fabric token ID
     * @return Balance on Besu
     */
    function getFabricTokenBalance(string memory fabricTokenId) external view returns (uint256) {
        return fabricTokenBalances[fabricTokenId];
    }

    /**
     * @notice Check if Fabric tx has been processed
     * @param fabricTxHash Fabric transaction hash
     * @return True if processed
     */
    function isFabricTxProcessed(bytes32 fabricTxHash) external view returns (bool) {
        return processedFabricTxs[fabricTxHash];
    }

    /**
     * @notice Override decimals to return 18
     */
    function decimals() public pure override returns (uint8) {
        return DECIMALS;
    }

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

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