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

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

/**
 * @title RateAccrual
 * @notice Applies time-based interest to outstanding debt using continuous compounding
 * @dev Similar to Aave's interest rate model
 */
contract RateAccrual is IRateAccrual, AccessControl {
    bytes32 public constant RATE_MANAGER_ROLE = keccak256("RATE_MANAGER_ROLE");

    uint256 public constant BASIS_POINTS = 10000;
    uint256 public constant SECONDS_PER_YEAR = 365 days;
    uint256 public constant RAY = 1e27; // Used for precision in calculations

    // Asset => interest rate (in basis points, e.g., 500 = 5% annual)
    mapping(address => uint256) private _interestRates;

    // Asset => rate accumulator (starts at RAY, increases over time)
    mapping(address => uint256) private _rateAccumulators;

    // Asset => last update timestamp
    mapping(address => uint256) private _lastUpdate;

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

    /**
     * @notice Accrue interest for an asset
     * @param asset Asset address
     * @return newAccumulator Updated rate accumulator
     */
    function accrueInterest(address asset) external override returns (uint256 newAccumulator) {
        uint256 oldAccumulator = _rateAccumulators[asset];
        uint256 lastUpdate = _lastUpdate[asset];

        if (lastUpdate == 0) {
            // Initialize accumulator
            _rateAccumulators[asset] = RAY;
            _lastUpdate[asset] = block.timestamp;
            return RAY;
        }

        if (block.timestamp == lastUpdate) {
            return oldAccumulator;
        }

        uint256 rate = _interestRates[asset];
        if (rate == 0) {
            return oldAccumulator;
        }

        // Calculate time elapsed in years
        uint256 timeElapsed = block.timestamp - lastUpdate;
        uint256 timeInYears = (timeElapsed * RAY) / SECONDS_PER_YEAR;

        // Continuous compounding: newAccumulator = oldAccumulator * e^(rate * time)
        // Approximation: e^(r*t) ≈ 1 + r*t + (r*t)^2/2! + ...
        // For small rates: e^(r*t) ≈ 1 + r*t (first order approximation)
        // More accurate: use compound interest formula with high precision
        
        // Convert rate from basis points to RAY: rateInRay = (rate * RAY) / BASIS_POINTS
        uint256 rateInRay = (rate * RAY) / BASIS_POINTS;
        
        // Calculate exponent: rateInRay * timeInYears / RAY
        uint256 exponent = (rateInRay * timeInYears) / RAY;
        
        // newAccumulator = oldAccumulator * (1 + exponent)
        // For better precision, we use: newAccumulator = oldAccumulator + (oldAccumulator * exponent) / RAY
        newAccumulator = oldAccumulator + (oldAccumulator * exponent) / RAY;

        _rateAccumulators[asset] = newAccumulator;
        _lastUpdate[asset] = block.timestamp;

        emit InterestAccrued(asset, oldAccumulator, newAccumulator);
    }

    /**
     * @notice Get current rate accumulator for an asset (accrues interest if needed)
     * @param asset Asset address
     * @return accumulator Current rate accumulator
     */
    function getRateAccumulator(address asset) external view override returns (uint256 accumulator) {
        accumulator = _rateAccumulators[asset];
        uint256 lastUpdate = _lastUpdate[asset];

        if (lastUpdate == 0) {
            return RAY; // Initial value
        }

        if (block.timestamp == lastUpdate) {
            return accumulator;
        }

        uint256 rate = _interestRates[asset];
        if (rate == 0) {
            return accumulator;
        }

        // Calculate accrued interest (same logic as accrueInterest but view-only)
        uint256 timeElapsed = block.timestamp - lastUpdate;
        uint256 timeInYears = (timeElapsed * RAY) / SECONDS_PER_YEAR;
        uint256 rateInRay = (rate * RAY) / BASIS_POINTS;
        uint256 exponent = (rateInRay * timeInYears) / RAY;
        accumulator = accumulator + (accumulator * exponent) / RAY;
    }

    /**
     * @notice Set interest rate for an asset
     * @param asset Asset address
     * @param rate Annual interest rate in basis points (e.g., 500 = 5%)
     */
    function setInterestRate(address asset, uint256 rate) external onlyRole(RATE_MANAGER_ROLE) {
        require(rate <= BASIS_POINTS * 100, "RateAccrual: rate too high"); // Max 100% annual
        
        // Accrue interest before updating rate
        if (_lastUpdate[asset] > 0) {
            this.accrueInterest(asset);
        } else {
            _rateAccumulators[asset] = RAY;
            _lastUpdate[asset] = block.timestamp;
        }

        _interestRates[asset] = rate;

        emit InterestRateSet(asset, rate);
    }

    /**
     * @notice Get interest rate for an asset
     * @param asset Asset address
     * @return rate Annual interest rate in basis points
     */
    function interestRate(address asset) external view override returns (uint256) {
        return _interestRates[asset];
    }

    /**
     * @notice Calculate debt with accrued interest
     * @param asset Asset address
     * @param principal Principal debt amount
     * @return debtWithInterest Debt amount with accrued interest
     */
    function calculateDebtWithInterest(address asset, uint256 principal) external view override returns (uint256 debtWithInterest) {
        uint256 accumulator = this.getRateAccumulator(asset);
        // debtWithInterest = principal * accumulator / RAY
        debtWithInterest = (principal * accumulator) / RAY;
    }
}
