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

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "../../../reserve/IReserveSystem.sol";
import "./IISOCurrencyManager.sol";

/**
 * @title ISOCurrencyManager
 * @notice Manages all ISO-4217 currencies with XAU triangulation support
 * @dev All currency conversions go through XAU: CurrencyA → XAU → CurrencyB
 */
contract ISOCurrencyManager is IISOCurrencyManager, Ownable, ReentrancyGuard {
    IReserveSystem public immutable reserveSystem;

    // XAU address (gold) - base anchor for all triangulations
    address public xauAddress;

    struct Currency {
        string currencyCode; // ISO-4217 code (USD, EUR, GBP, etc.)
        address tokenAddress; // Token contract address (if tokenized)
        uint256 xauRate; // Rate: 1 oz XAU = xauRate units of currency (in 18 decimals)
        bool isActive;
        bool isTokenized; // Whether currency has on-chain token representation
    }

    mapping(string => Currency) public currencies;
    string[] public supportedCurrencies;

    // Example rates (in 18 decimals):
    // USD: 1 oz XAU = 2000 USD, so xauRate = 2000e18
    // EUR: 1 oz XAU = 1800 EUR, so xauRate = 1800e18

    event CurrencyRegistered(
        string indexed currencyCode,
        address tokenAddress,
        uint256 xauRate,
        bool isTokenized
    );

    event CurrencyConverted(
        string fromCurrency,
        string toCurrency,
        uint256 fromAmount,
        uint256 toAmount
    );

    error ZeroAddress();
    error CurrencyNotRegistered();
    error InvalidXauRate();
    error XauNotSet();
    error InvalidCurrencyCode();

    /**
     * @notice Constructor
     * @param _reserveSystem ReserveSystem contract address
     */
    constructor(address _reserveSystem) Ownable(msg.sender) {
        if (_reserveSystem == address(0)) revert ZeroAddress();
        reserveSystem = IReserveSystem(_reserveSystem);
    }

    /**
     * @notice Set XAU (gold) address
     * @param _xauAddress XAU token address
     */
    function setXAUAddress(address _xauAddress) external onlyOwner {
        if (_xauAddress == address(0)) revert ZeroAddress();
        xauAddress = _xauAddress;
    }

    /**
     * @notice Register ISO-4217 currency
     * @param currencyCode ISO-4217 currency code (USD, EUR, GBP, JPY, etc.)
     * @param tokenAddress Token contract address (address(0) if not tokenized)
     * @param xauRate Rate: 1 oz XAU = xauRate units of currency (in 18 decimals)
     * @return success Whether registration was successful
     */
    function registerCurrency(
        string memory currencyCode,
        address tokenAddress,
        uint256 xauRate
    ) external override onlyOwner returns (bool) {
        if (bytes(currencyCode).length == 0) revert InvalidCurrencyCode();
        if (xauRate == 0) revert InvalidXauRate();
        if (xauAddress == address(0)) revert XauNotSet();

        bool isTokenized = tokenAddress != address(0);

        currencies[currencyCode] = Currency({
            currencyCode: currencyCode,
            tokenAddress: tokenAddress,
            xauRate: xauRate,
            isActive: true,
            isTokenized: isTokenized
        });

        // Add to supported currencies if not already present
        bool alreadyAdded = false;
        for (uint256 i = 0; i < supportedCurrencies.length; i++) {
            if (keccak256(bytes(supportedCurrencies[i])) == keccak256(bytes(currencyCode))) {
                alreadyAdded = true;
                break;
            }
        }
        if (!alreadyAdded) {
            supportedCurrencies.push(currencyCode);
        }

        emit CurrencyRegistered(currencyCode, tokenAddress, xauRate, isTokenized);
        return true;
    }

    /**
     * @notice Convert between currencies via XAU triangulation
     * @param fromCurrency Source currency code
     * @param toCurrency Target currency code
     * @param amount Amount to convert
     * @return targetAmount Amount in target currency
     */
    function convertViaXAU(
        string memory fromCurrency,
        string memory toCurrency,
        uint256 amount
    ) external view override returns (uint256 targetAmount) {
        Currency memory from = currencies[fromCurrency];
        Currency memory to = currencies[toCurrency];
        
        if (bytes(from.currencyCode).length == 0) revert CurrencyNotRegistered();
        if (bytes(to.currencyCode).length == 0) revert CurrencyNotRegistered();
        if (xauAddress == address(0)) revert XauNotSet();

        // Step 1: Convert fromCurrency to XAU
        // amount / from.xauRate = XAU amount
        uint256 xauAmount = (amount * 1e18) / from.xauRate;

        // Step 2: Convert XAU to toCurrency
        // xauAmount * to.xauRate / 1e18 = targetAmount
        targetAmount = (xauAmount * to.xauRate) / 1e18;

        return targetAmount;
    }

    /**
     * @notice Get exchange rate for currency pair
     * @param fromCurrency Source currency code
     * @param toCurrency Target currency code
     * @return rate Exchange rate (toCurrency per fromCurrency, in 18 decimals)
     */
    function getCurrencyRate(
        string memory fromCurrency,
        string memory toCurrency
    ) external view override returns (uint256 rate) {
        Currency memory from = currencies[fromCurrency];
        Currency memory to = currencies[toCurrency];
        
        if (bytes(from.currencyCode).length == 0) revert CurrencyNotRegistered();
        if (bytes(to.currencyCode).length == 0) revert CurrencyNotRegistered();

        // Rate = (to.xauRate / from.xauRate) * 1e18
        // This gives: 1 fromCurrency = rate toCurrency
        rate = (to.xauRate * 1e18) / from.xauRate;

        return rate;
    }

    /**
     * @notice Get all supported currencies
     * @return Array of currency codes
     */
    function getAllSupportedCurrencies() external view override returns (string[] memory) {
        return supportedCurrencies;
    }

    /**
     * @notice Get token address for a currency code
     * @param currencyCode ISO-4217 currency code
     * @return Token address (address(0) if not tokenized)
     */
    function getCurrencyAddress(
        string memory currencyCode
    ) external view override returns (address) {
        Currency memory currency = currencies[currencyCode];
        if (bytes(currency.currencyCode).length == 0) revert CurrencyNotRegistered();
        return currency.tokenAddress;
    }

    /**
     * @notice Update XAU rate for a currency
     * @param currencyCode ISO-4217 currency code
     * @param newXauRate New XAU rate
     */
    function updateXauRate(string memory currencyCode, uint256 newXauRate) external onlyOwner {
        Currency storage currency = currencies[currencyCode];
        if (bytes(currency.currencyCode).length == 0) revert CurrencyNotRegistered();
        if (newXauRate == 0) revert InvalidXauRate();
        
        currency.xauRate = newXauRate;
    }

    /**
     * @notice Get currency info
     * @param currencyCode ISO-4217 currency code
     * @return tokenAddress Token address
     * @return xauRate XAU rate
     * @return isActive Whether currency is active
     * @return isTokenized Whether currency is tokenized
     */
    function getCurrencyInfo(
        string memory currencyCode
    ) external view returns (
        address tokenAddress,
        uint256 xauRate,
        bool isActive,
        bool isTokenized
    ) {
        Currency memory currency = currencies[currencyCode];
        if (bytes(currency.currencyCode).length == 0) revert CurrencyNotRegistered();
        
        return (
            currency.tokenAddress,
            currency.xauRate,
            currency.isActive,
            currency.isTokenized
        );
    }

    /**
     * @notice Batch register currencies
     * @param currencyCodes Array of currency codes
     * @param tokenAddresses Array of token addresses
     * @param xauRates Array of XAU rates
     */
    function batchRegisterCurrencies(
        string[] memory currencyCodes,
        address[] memory tokenAddresses,
        uint256[] memory xauRates
    ) external onlyOwner {
        require(
            currencyCodes.length == tokenAddresses.length && 
            currencyCodes.length == xauRates.length,
            "ISOCurrencyManager: length mismatch"
        );

        for (uint256 i = 0; i < currencyCodes.length; i++) {
            // Call internal registration logic directly
            bool isTokenized = tokenAddresses[i] != address(0);
            currencies[currencyCodes[i]] = Currency({
                currencyCode: currencyCodes[i],
                tokenAddress: tokenAddresses[i],
                xauRate: xauRates[i],
                isActive: true,
                isTokenized: isTokenized
            });

            // Add to supported currencies if not already present
            bool alreadyAdded = false;
            for (uint256 j = 0; j < supportedCurrencies.length; j++) {
                if (keccak256(bytes(supportedCurrencies[j])) == keccak256(bytes(currencyCodes[i]))) {
                    alreadyAdded = true;
                    break;
                }
            }
            if (!alreadyAdded) {
                supportedCurrencies.push(currencyCodes[i]);
            }

            emit CurrencyRegistered(currencyCodes[i], tokenAddresses[i], xauRates[i], isTokenized);
        }
    }
}

