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

import "@openzeppelin/contracts/access/AccessControl.sol";
import "./interfaces/ILedger.sol";
import "./interfaces/IXAUOracle.sol";
import "./interfaces/IRateAccrual.sol";

/**
 * @title Ledger
 * @notice Core ledger for tracking collateral and debt balances
 * @dev Single source of truth for all vault accounting
 * 
 * COMPLIANCE NOTES:
 * - All valuations are normalized to XAU (gold) as the universal unit of account
 * - eMoney tokens are XAU-denominated (1 eMoney = 1 XAU equivalent)
 * - GRU (Global Reserve Unit) is a NON-ISO 4217 synthetic unit of account, NOT legal tender
 * - All currency conversions MUST go through XAU triangulation
 * - ISO 4217 currency codes are validated where applicable
 */
contract Ledger is ILedger, AccessControl {
    bytes32 public constant VAULT_ROLE = keccak256("VAULT_ROLE");
    bytes32 public constant PARAM_MANAGER_ROLE = keccak256("PARAM_MANAGER_ROLE");

    // Collateral balances: vault => asset => amount
    mapping(address => mapping(address => uint256)) public override collateral;

    // Debt balances: vault => currency => amount
    mapping(address => mapping(address => uint256)) public override debt;

    // Risk parameters per asset
    mapping(address => uint256) public override debtCeiling;
    mapping(address => uint256) public override liquidationRatio; // in basis points
    mapping(address => uint256) public override creditMultiplier; // in basis points (50000 = 5x)
    mapping(address => uint256) public override rateAccumulator; // debt interest accumulator

    // System contracts
    IXAUOracle public xauOracle;
    IRateAccrual public rateAccrual;

    // Track registered assets
    mapping(address => bool) public isRegisteredAsset;

    constructor(address admin, address xauOracle_, address rateAccrual_) {
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(PARAM_MANAGER_ROLE, admin);
        xauOracle = IXAUOracle(xauOracle_);
        rateAccrual = IRateAccrual(rateAccrual_);
    }

    /**
     * @notice Modify collateral balance for a vault
     * @param vault Vault address
     * @param asset Collateral asset address
     * @param delta Amount to add (positive) or subtract (negative)
     */
    function modifyCollateral(address vault, address asset, int256 delta) external override onlyRole(VAULT_ROLE) {
        require(isRegisteredAsset[asset], "Ledger: asset not registered");
        
        uint256 currentBalance = collateral[vault][asset];
        
        if (delta > 0) {
            collateral[vault][asset] = currentBalance + uint256(delta);
        } else if (delta < 0) {
            uint256 decrease = uint256(-delta);
            require(currentBalance >= decrease, "Ledger: insufficient collateral");
            collateral[vault][asset] = currentBalance - decrease;
        }

        emit CollateralModified(vault, asset, delta);
    }

    /**
     * @notice Modify debt balance for a vault
     * @param vault Vault address
     * @param currency Debt currency address (eMoney token)
     * @param delta Amount to add (positive) or subtract (negative)
     */
    function modifyDebt(address vault, address currency, int256 delta) external override onlyRole(VAULT_ROLE) {
        // Accrue interest before modifying debt
        rateAccrual.accrueInterest(currency);
        uint256 accumulator = rateAccrual.getRateAccumulator(currency);
        rateAccumulator[currency] = accumulator;

        uint256 currentDebt = debt[vault][currency];
        
        if (delta > 0) {
            uint256 addAmount = uint256(delta);
            debt[vault][currency] = currentDebt + addAmount;
            _totalDebtForCurrency[currency] += addAmount;
            _trackCurrency(currency);
        } else if (delta < 0) {
            uint256 decrease = uint256(-delta);
            require(currentDebt >= decrease, "Ledger: insufficient debt");
            debt[vault][currency] = currentDebt - decrease;
            _totalDebtForCurrency[currency] -= decrease;
        }

        emit DebtModified(vault, currency, delta);
    }

    /**
     * @notice Get vault health (collateralization ratio in XAU)
     * @param vault Vault address
     * @return healthRatio Collateralization ratio in basis points (10000 = 100%)
     * @return collateralValue Total collateral value in XAU (18 decimals)
     * @return debtValue Total debt value in XAU (18 decimals)
     */
    function getVaultHealth(address vault) external view override returns (
        uint256 healthRatio,
        uint256 collateralValue,
        uint256 debtValue
    ) {
        collateralValue = _calculateCollateralValue(vault);
        debtValue = _calculateDebtValue(vault);

        if (debtValue == 0) {
            // No debt = infinite health
            healthRatio = type(uint256).max;
        } else {
            // healthRatio = (collateralValue / debtValue) * 10000
            healthRatio = (collateralValue * 10000) / debtValue;
        }
    }

    /**
     * @notice Check if a vault can borrow a specific amount
     * @param vault Vault address
     * @param currency Debt currency address
     * @param amount Amount to borrow (in currency units)
     * @return borrowable True if borrow is allowed
     * @return reasonCode Reason code if borrow is not allowed
     */
    function canBorrow(address vault, address currency, uint256 amount) external view override returns (
        bool borrowable,
        bytes32 reasonCode
    ) {
        // Get current collateral and debt values in XAU
        uint256 collateralValue = _calculateCollateralValue(vault);
        uint256 currentDebtValue = _calculateDebtValue(vault);

        // Calculate new debt value in XAU
        // eMoney is XAU-denominated by design: 1 eMoney = 1 XAU equivalent
        // MANDATORY: If non-XAU currencies are used, they MUST be triangulated through XAU
        uint256 newDebtValue = currentDebtValue + amount;

        // Check debt ceiling
        uint256 totalDebt = _getTotalDebtForCurrency(currency);
        if (totalDebt + amount > debtCeiling[currency]) {
            return (false, keccak256("DEBT_CEILING_EXCEEDED"));
        }

        // Check collateralization ratio
        // Apply credit multiplier: maxBorrowValue = collateralValue * creditMultiplier / 10000
        uint256 maxBorrowValue = (collateralValue * creditMultiplier[currency]) / 10000;
        
        if (newDebtValue > maxBorrowValue) {
            return (false, keccak256("INSUFFICIENT_COLLATERAL"));
        }

        // Check minimum collateralization ratio
        uint256 healthRatio = (collateralValue * 10000) / newDebtValue;
        uint256 minRatio = liquidationRatio[currency] + 100; // Add 1% buffer above liquidation ratio
        
        if (healthRatio < minRatio) {
            return (false, keccak256("BELOW_MIN_COLLATERALIZATION"));
        }

        return (true, bytes32(0));
    }

    /**
     * @notice Set risk parameters for an asset
     * @param asset Asset address
     * @param debtCeiling_ Debt ceiling
     * @param liquidationRatio_ Liquidation ratio in basis points
     * @param creditMultiplier_ Credit multiplier in basis points
     */
    function setRiskParameters(
        address asset,
        uint256 debtCeiling_,
        uint256 liquidationRatio_,
        uint256 creditMultiplier_
    ) external onlyRole(PARAM_MANAGER_ROLE) {
        require(liquidationRatio_ > 0 && liquidationRatio_ <= 10000, "Ledger: invalid liquidation ratio");
        require(creditMultiplier_ > 0 && creditMultiplier_ <= 100000, "Ledger: invalid credit multiplier"); // Max 10x

        isRegisteredAsset[asset] = true;
        debtCeiling[asset] = debtCeiling_;
        liquidationRatio[asset] = liquidationRatio_;
        creditMultiplier[asset] = creditMultiplier_;

        emit RiskParametersSet(asset, debtCeiling_, liquidationRatio_, creditMultiplier_);
    }

    /**
     * @notice Calculate total collateral value in XAU for a vault
     * @param vault Vault address
     * @return value Total value in XAU (18 decimals)
     */
    function _calculateCollateralValue(address vault) internal view returns (uint256 value) {
        // For ETH collateral, get price from oracle
        // In production, would iterate over all collateral assets
        // For now, assume only ETH is supported
        
        // Get ETH balance
        uint256 ethBalance = collateral[vault][address(0)]; // address(0) represents ETH
        
        if (ethBalance == 0) {
            return 0;
        }

        // Get ETH/XAU price from oracle
        (uint256 ethPriceInXAU, ) = xauOracle.getETHPriceInXAU();
        
        // Calculate value: ethBalance * ethPriceInXAU / 1e18
        value = (ethBalance * ethPriceInXAU) / 1e18;
    }

    // Track currencies with debt for iteration
    address[] private _currenciesWithDebt;
    mapping(address => bool) private _isTrackedCurrency;

    // Total debt per currency (for debt ceiling check)
    mapping(address => uint256) private _totalDebtForCurrency;

    /**
     * @notice Calculate total debt value in XAU for a vault
     * @param vault Vault address
     * @return value Total debt value in XAU (18 decimals)
     * @dev MANDATORY COMPLIANCE: eMoney tokens are XAU-denominated
     *      All debt is normalized to XAU terms for consistent valuation
     *      If non-XAU currencies are used, they MUST be triangulated through XAU
     */
    function _calculateDebtValue(address vault) internal view returns (uint256 value) {
        // eMoney tokens are XAU-denominated by design: 1 eMoney = 1 XAU equivalent
        // This ensures all debt valuations are consistent in XAU terms
        // If other currencies are used, they MUST be converted via XAU triangulation
        
        // Iterate over tracked currencies
        for (uint256 i = 0; i < _currenciesWithDebt.length; i++) {
            address currency = _currenciesWithDebt[i];
            uint256 debtAmount = debt[vault][currency];
            
            if (debtAmount > 0) {
                // Apply interest accrual
                uint256 accumulator = rateAccrual.getRateAccumulator(currency);
                uint256 debtWithInterest = (debtAmount * accumulator) / 1e27;
                
                // eMoney is XAU-denominated: 1:1 conversion
                // For other currencies, XAU triangulation would be applied here
                value += debtWithInterest;
            }
        }
    }

    /**
     * @notice Get total debt for a currency across all vaults
     * @param currency Currency address
     * @return total Total debt
     */
    function _getTotalDebtForCurrency(address currency) internal view returns (uint256 total) {
        return _totalDebtForCurrency[currency];
    }

    /**
     * @notice Track a currency when debt is created
     * @param currency Currency address
     */
    function _trackCurrency(address currency) internal {
        if (!_isTrackedCurrency[currency]) {
            _currenciesWithDebt.push(currency);
            _isTrackedCurrency[currency] = true;
        }
    }

    /**
     * @notice Set XAU Oracle address
     * @param xauOracle_ New oracle address
     */
    function setXAUOracle(address xauOracle_) external onlyRole(DEFAULT_ADMIN_ROLE) {
        require(xauOracle_ != address(0), "Ledger: zero address");
        xauOracle = IXAUOracle(xauOracle_);
    }

    /**
     * @notice Set Rate Accrual address
     * @param rateAccrual_ New rate accrual address
     */
    function setRateAccrual(address rateAccrual_) external onlyRole(DEFAULT_ADMIN_ROLE) {
        require(rateAccrual_ != address(0), "Ledger: zero address");
        rateAccrual = IRateAccrual(rateAccrual_);
    }

    /**
     * @notice Grant VAULT_ROLE to an address (for factory use)
     * @param account Address to grant role to
     */
    function grantVaultRole(address account) external onlyRole(DEFAULT_ADMIN_ROLE) {
        _grantRole(VAULT_ROLE, account);
    }
}
