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

import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "../interfaces/IReserveOracle.sol";

/**
 * @title ReserveOracle
 * @notice Quorum-based oracle system for verifying fiat reserves
 * @dev Requires quorum of oracle reports before accepting reserve values
 */
contract ReserveOracle is IReserveOracle, AccessControl, ReentrancyGuard {
    bytes32 public constant ORACLE_ROLE = keccak256("ORACLE_ROLE");

    uint256 public quorumThreshold; // Number of reports required for quorum (default: 3)
    uint256 public stalenessThreshold; // Maximum age of reports in seconds (default: 3600 = 1 hour)

    // Currency code => ReserveReport[]
    mapping(string => ReserveReport[]) private _reports;
    
    // Currency code => verified reserve (consensus value)
    mapping(string => uint256) private _verifiedReserves;
    mapping(string => uint256) private _lastUpdate;

    // Track valid oracles
    mapping(address => bool) public isOracle;

    constructor(address admin, uint256 quorumThreshold_, uint256 stalenessThreshold_) {
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(ORACLE_ROLE, admin);
        quorumThreshold = quorumThreshold_;
        stalenessThreshold = stalenessThreshold_;
        isOracle[admin] = true;
    }

    /**
     * @notice Submit reserve report for a currency
     * @param currencyCode ISO-4217 currency code
     * @param reserveBalance Reserve balance in base currency units
     * @param attestationHash Hash of custodian attestation
     */
    function submitReserveReport(
        string memory currencyCode,
        uint256 reserveBalance,
        bytes32 attestationHash
    ) external override onlyRole(ORACLE_ROLE) nonReentrant {
        require(isOracle[msg.sender], "ReserveOracle: not authorized oracle");
        require(reserveBalance > 0, "ReserveOracle: zero reserve");
        
        // Validate ISO-4217 format (basic check)
        bytes memory codeBytes = bytes(currencyCode);
        require(codeBytes.length == 3, "ReserveOracle: invalid currency code format");
        
        // Remove stale reports
        _removeStaleReports(currencyCode);
        
        // Add new report
        _reports[currencyCode].push(ReserveReport({
            reporter: msg.sender,
            reserveBalance: reserveBalance,
            timestamp: block.timestamp,
            attestationHash: attestationHash,
            isValid: true
        }));
        
        emit ReserveReportSubmitted(currencyCode, msg.sender, reserveBalance, block.timestamp);
        
        // Check if quorum is met and update verified reserve
        (bool quorumMet, ) = this.isQuorumMet(currencyCode);
        if (quorumMet) {
            uint256 consensusReserve = this.getConsensusReserve(currencyCode);
            _verifiedReserves[currencyCode] = consensusReserve;
            _lastUpdate[currencyCode] = block.timestamp;
            emit QuorumMet(currencyCode, consensusReserve);
        }
    }

    /**
     * @notice Get verified reserve balance for a currency
     * @param currencyCode ISO-4217 currency code
     * @return reserveBalance Verified reserve balance
     * @return timestamp Last update timestamp
     */
    function getVerifiedReserve(string memory currencyCode) external view override returns (
        uint256 reserveBalance,
        uint256 timestamp
    ) {
        return (_verifiedReserves[currencyCode], _lastUpdate[currencyCode]);
    }

    /**
     * @notice Check if oracle quorum is met for a currency
     * @param currencyCode ISO-4217 currency code
     * @return quorumMet True if quorum is met
     * @return reportCount Number of valid reports
     */
    function isQuorumMet(string memory currencyCode) external view override returns (bool quorumMet, uint256 reportCount) {
        ReserveReport[] memory reports = _reports[currencyCode];
        uint256 validCount = 0;
        
        uint256 cutoffTime = block.timestamp > stalenessThreshold ? block.timestamp - stalenessThreshold : 0;
        
        for (uint256 i = 0; i < reports.length; i++) {
            if (reports[i].isValid && reports[i].timestamp >= cutoffTime) {
                validCount++;
            }
        }
        
        reportCount = validCount;
        quorumMet = validCount >= quorumThreshold;
    }

    /**
     * @notice Get consensus reserve balance (median/average of quorum reports)
     * @param currencyCode ISO-4217 currency code
     * @return consensusReserve Consensus reserve balance
     */
    function getConsensusReserve(string memory currencyCode) external view override returns (uint256 consensusReserve) {
        ReserveReport[] memory reports = _reports[currencyCode];
        
        // Remove stale and invalid reports
        uint256[] memory validReserves = new uint256[](reports.length);
        uint256 validCount = 0;
        uint256 cutoffTime = block.timestamp > stalenessThreshold ? block.timestamp - stalenessThreshold : 0;
        
        for (uint256 i = 0; i < reports.length; i++) {
            if (reports[i].isValid && reports[i].timestamp >= cutoffTime) {
                validReserves[validCount] = reports[i].reserveBalance;
                validCount++;
            }
        }
        
        if (validCount == 0) {
            return 0;
        }
        
        // Sort and calculate median (simple average if count is even)
        // For simplicity, calculate average (in production, use median for robustness)
        uint256 sum = 0;
        for (uint256 i = 0; i < validCount; i++) {
            sum += validReserves[i];
        }
        
        consensusReserve = sum / validCount;
    }

    /**
     * @notice Remove stale reports for a currency
     * @param currencyCode ISO-4217 currency code
     */
    function _removeStaleReports(string memory currencyCode) internal {
        ReserveReport[] storage reports = _reports[currencyCode];
        uint256 cutoffTime = block.timestamp > stalenessThreshold ? block.timestamp - stalenessThreshold : 0;
        
        // Mark stale reports as invalid
        for (uint256 i = 0; i < reports.length; i++) {
            if (reports[i].timestamp < cutoffTime) {
                reports[i].isValid = false;
            }
        }
    }

    /**
     * @notice Add oracle
     * @param oracle Oracle address
     */
    function addOracle(address oracle) external onlyRole(DEFAULT_ADMIN_ROLE) {
        require(oracle != address(0), "ReserveOracle: zero address");
        isOracle[oracle] = true;
        _grantRole(ORACLE_ROLE, oracle);
    }

    /**
     * @notice Remove oracle
     * @param oracle Oracle address
     */
    function removeOracle(address oracle) external onlyRole(DEFAULT_ADMIN_ROLE) {
        require(isOracle[oracle], "ReserveOracle: not an oracle");
        isOracle[oracle] = false;
        _revokeRole(ORACLE_ROLE, oracle);
    }

    /**
     * @notice Set quorum threshold
     * @param threshold New quorum threshold
     */
    function setQuorumThreshold(uint256 threshold) external onlyRole(DEFAULT_ADMIN_ROLE) {
        require(threshold > 0, "ReserveOracle: zero threshold");
        quorumThreshold = threshold;
    }

    /**
     * @notice Set staleness threshold
     * @param threshold New staleness threshold in seconds
     */
    function setStalenessThreshold(uint256 threshold) external onlyRole(DEFAULT_ADMIN_ROLE) {
        stalenessThreshold = threshold;
    }

    /**
     * @notice Get reports for a currency
     * @param currencyCode ISO-4217 currency code
     * @return reports Array of reserve reports
     */
    function getReports(string memory currencyCode) external view returns (ReserveReport[] memory reports) {
        return _reports[currencyCode];
    }
}
