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

import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "../vendor/openzeppelin/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "../vendor/openzeppelin/ReentrancyGuardUpgradeable.sol";
import "./interfaces/IISO4217WToken.sol";
import "./libraries/ISO4217WCompliance.sol";

/**
 * @title ISO4217WToken
 * @notice ISO-4217 W token (e.g., USDW, EURW, GBPW) - M1 eMoney token
 * @dev Represents 1:1 redeemable digital claim on fiat currency
 * 
 * COMPLIANCE:
 * - Classification: M1 eMoney
 * - Legal Tender: NO
 * - Synthetic / Reserve Unit: NO
 * - Commodity-Backed: NO
 * - Money Multiplier: m = 1.0 (hard-fixed, no fractional reserve)
 * - Backing: 1:1 with fiat currency in segregated custodial accounts
 * - GRU Isolation: Direct/indirect GRU conversion prohibited
 */
contract ISO4217WToken is 
    IISO4217WToken,
    Initializable,
    ERC20Upgradeable,
    AccessControlUpgradeable,
    UUPSUpgradeable,
    ReentrancyGuardUpgradeable
{
    using ISO4217WCompliance for *;
    
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
    bytes32 public constant RESERVE_UPDATE_ROLE = keccak256("RESERVE_UPDATE_ROLE");

    string private _currencyCode; // ISO-4217 code (e.g., "USD")
    uint8 private _decimals; // Token decimals (typically 2 for fiat)
    uint256 private _verifiedReserve; // Verified reserve balance in base currency units
    address private _custodian;
    address private _mintController;
    address private _burnController;
    address private _complianceGuard;

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

    /**
     * @notice Initialize the ISO-4217 W token
     * @param name Token name (e.g., "USDW Token")
     * @param symbol Token symbol (e.g., "USDW")
     * @param currencyCode_ ISO-4217 currency code (e.g., "USD")
     * @param decimals_ Token decimals (typically 2 for fiat)
     * @param custodian_ Custodian address
     * @param mintController_ Mint controller address
     * @param burnController_ Burn controller address
     * @param complianceGuard_ Compliance guard address
     * @param admin Admin address
     */
    function initialize(
        string memory name,
        string memory symbol,
        string memory currencyCode_,
        uint8 decimals_,
        address custodian_,
        address mintController_,
        address burnController_,
        address complianceGuard_,
        address admin
    ) external initializer {
        __ERC20_init(name, symbol);
        __AccessControl_init();
        __UUPSUpgradeable_init();
        __ReentrancyGuard_init();
        
        // Validate ISO-4217 format
        require(
            ISO4217WCompliance.isValidISO4217Format(currencyCode_),
            "ISO4217WToken: invalid ISO-4217 format"
        );
        
        // Validate token symbol matches <CCC>W pattern
        require(
            ISO4217WCompliance.validateTokenSymbol(currencyCode_, symbol),
            "ISO4217WToken: token symbol must be <CCC>W"
        );
        
        // Validate GRU isolation
        require(
            !ISO4217WCompliance.violatesGRUIsolation(currencyCode_),
            "ISO4217WToken: GRU isolation violation"
        );
        
        require(custodian_ != address(0), "ISO4217WToken: zero custodian");
        require(mintController_ != address(0), "ISO4217WToken: zero mint controller");
        require(burnController_ != address(0), "ISO4217WToken: zero burn controller");
        require(complianceGuard_ != address(0), "ISO4217WToken: zero compliance guard");
        
        _currencyCode = currencyCode_;
        _decimals = decimals_;
        _custodian = custodian_;
        _mintController = mintController_;
        _burnController = burnController_;
        _complianceGuard = complianceGuard_;
        
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(MINTER_ROLE, mintController_);
        _grantRole(BURNER_ROLE, burnController_);
    }

    /**
     * @notice Get the ISO-4217 currency code this token represents
     * @return currencyCode 3-letter ISO-4217 code
     */
    function currencyCode() external view override returns (string memory) {
        return _currencyCode;
    }

    /**
     * @notice Override totalSupply to resolve multiple inheritance conflict
     * @return Total supply of tokens
     */
    function totalSupply() public view override(ERC20Upgradeable, IISO4217WToken) returns (uint256) {
        return super.totalSupply();
    }

    /**
     * @notice Get verified reserve balance
     * @return reserveBalance Reserve balance in base currency units
     */
    function verifiedReserve() external view override returns (uint256) {
        return _verifiedReserve;
    }

    /**
     * @notice Check if reserves are sufficient
     * @dev Reserve MUST be >= supply (enforcing 1:1 backing)
     * @return isSufficient True if verifiedReserve >= totalSupply
     */
    function isReserveSufficient() external view override returns (bool) {
        return ISO4217WCompliance.isReserveSufficient(_verifiedReserve, totalSupply());
    }

    /**
     * @notice Get custodian address
     */
    function custodian() external view override returns (address) {
        return _custodian;
    }

    /**
     * @notice Get mint controller address
     */
    function mintController() external view override returns (address) {
        return _mintController;
    }

    /**
     * @notice Get burn controller address
     */
    function burnController() external view override returns (address) {
        return _burnController;
    }

    /**
     * @notice Get compliance guard address
     */
    function complianceGuard() external view override returns (address) {
        return _complianceGuard;
    }

    /**
     * @notice Update verified reserve (oracle/attestation)
     * @dev Can only be called by authorized reserve update role
     * @param newReserve New reserve balance
     */
    function updateVerifiedReserve(uint256 newReserve) external onlyRole(RESERVE_UPDATE_ROLE) {
        uint256 currentSupply = totalSupply();
        
        // Enforce money multiplier = 1.0
        // Reserve MUST be >= supply (1:1 backing or better)
        if (newReserve < currentSupply) {
            emit ReserveInsufficient(newReserve, currentSupply);
            // Do not revert - allow flagging for monitoring
        }
        
        _verifiedReserve = newReserve;
        emit ReserveUpdated(newReserve, block.timestamp);
    }

    /**
     * @notice Mint tokens (only by mint controller)
     * @param to Recipient address
     * @param amount Amount to mint
     */
    function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) nonReentrant {
        require(to != address(0), "ISO4217WToken: zero address");
        require(amount > 0, "ISO4217WToken: zero amount");
        
        uint256 currentSupply = totalSupply();
        uint256 newSupply = currentSupply + amount;
        
        // Enforce money multiplier = 1.0
        // Reserve MUST be >= new supply (1:1 backing)
        require(
            _verifiedReserve >= newSupply,
            "ISO4217WToken: reserve insufficient - money multiplier violation"
        );
        
        _mint(to, amount);
        emit Minted(to, amount, _currencyCode);
    }

    /**
     * @notice Burn tokens (only by burn controller)
     * @param from Source address
     * @param amount Amount to burn
     */
    function burn(address from, uint256 amount) external onlyRole(BURNER_ROLE) nonReentrant {
        require(amount > 0, "ISO4217WToken: zero amount");
        
        _burn(from, amount);
        emit Burned(from, amount, _currencyCode);
    }

    /**
     * @notice Override decimals (typically 2 for fiat currencies)
     */
    function decimals() public view virtual override returns (uint8) {
        return _decimals;
    }

    /**
     * @notice Authorize upgrade (UUPS)
     * @dev Only non-monetary components may be upgraded
     */
    function _authorizeUpgrade(address newImplementation) internal override onlyRole(DEFAULT_ADMIN_ROLE) {
        // In production, add checks to ensure monetary logic is immutable
        // Only allow upgrades to non-monetary components
    }
}
