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

import "@openzeppelin/contracts/access/Ownable2Step.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

/**
 * @title FlourishingMetricsOracleV3
 * @notice V3: Multi-source aggregation, TWAP, manipulation detection, emergency override.
 */
contract FlourishingMetricsOracleV3 is Ownable2Step, ReentrancyGuard {

    uint256 public constant MIN_SOURCES         = 3;
    uint256 public constant MAX_METRIC_AGE      = 1 hours;
    uint256 public constant DEVIATION_THRESHOLD = 2000; // 20% in bps

    error InsufficientSources();
    error MetricTooOld();
    error SourceNotRegistered();
    error SourceAlreadyRegistered();
    error ZeroAddress();
    error EmptyMetricId();

    struct MetricDataPoint {
        uint256 value;
        uint64  timestamp;
        address source;
    }

    struct AggregatedMetric {
        uint256 twap;
        uint256 latestValue;
        uint64  lastUpdated;
        uint256 sourceCount;
        bool    manipulationFlag;
    }

    struct SourceInfo {
        bool    registered;
        uint256 weight;       // bps, 10000 = 100%
        uint256 submissionCount;
    }

    mapping(bytes32 => AggregatedMetric)        public metrics;
    mapping(bytes32 => MetricDataPoint[])       public metricHistory;
    mapping(address => SourceInfo)              public sources;
    address[]                                   public sourceList;
    mapping(bytes32 => bool)                    public emergencyOverrideActive;

    event MetricUpdated(bytes32 indexed metricId, uint256 newValue, uint256 twap);
    event ManipulationFlagged(bytes32 indexed metricId, address source, uint256 deviation);
    event SourceRegistered(address indexed source, uint256 weight);
    event SourceDeregistered(address indexed source);
    event EmergencyOverride(bytes32 indexed metricId, uint256 value);
    event AggregationDeferred(bytes32 indexed metricId, uint256 activeCount, uint256 required);

    constructor() Ownable(msg.sender) {}

    function registerSource(address source, uint256 weight) external onlyOwner {
        if (source == address(0)) revert ZeroAddress();
        if (sources[source].registered) revert SourceAlreadyRegistered();
        sources[source] = SourceInfo(true, weight, 0);
        sourceList.push(source);
        emit SourceRegistered(source, weight);
    }

    function deregisterSource(address source) external onlyOwner {
        sources[source].registered = false;
        emit SourceDeregistered(source);
    }

    function submitMetric(bytes32 metricId, uint256 value) external nonReentrant {
        if (!sources[msg.sender].registered) revert SourceNotRegistered();
        if (metricId == bytes32(0))          revert EmptyMetricId();

        metricHistory[metricId].push(MetricDataPoint(value, uint64(block.timestamp), msg.sender));
        sources[msg.sender].submissionCount++;

        // Recompute aggregated metric
        _aggregate(metricId, value);
    }

    function _aggregate(bytes32 metricId, uint256 newValue) internal {
        MetricDataPoint[] storage history = metricHistory[metricId];
        uint256 len = history.length;

        // Need at least MIN_SOURCES data points
        uint256 activeCount = 0;
        uint256 sum = 0;
        bool manipulated = false;

        // Simple aggregation: use last N points, one per source
        for (uint256 i = len > 10 ? len - 10 : 0; i < len; i++) {
            sum += history[i].value;
            activeCount++;
        }

        if (activeCount == 0) return;
        if (activeCount < MIN_SOURCES) {
            emit AggregationDeferred(metricId, activeCount, MIN_SOURCES);
            return;
        }

        uint256 mean = sum / activeCount;

        // Manipulation detection: check if newValue deviates >20% from mean
        if (mean > 0) {
            uint256 diff = newValue > mean ? newValue - mean : mean - newValue;
            uint256 deviation = (diff * 10000) / mean;
            if (deviation > DEVIATION_THRESHOLD) {
                manipulated = true;
                emit ManipulationFlagged(metricId, msg.sender, deviation);
            }
        }

        // TWAP: weighted average of historical values
        AggregatedMetric storage m = metrics[metricId];
        uint256 twap = m.twap == 0 ? newValue : (m.twap * 3 + newValue) / 4; // EMA-like

        m.twap             = twap;
        m.latestValue      = newValue;
        m.lastUpdated      = uint64(block.timestamp);
        m.sourceCount      = activeCount;
        m.manipulationFlag = manipulated;

        emit MetricUpdated(metricId, newValue, twap);
    }

    function emergencyOverrideMetric(bytes32 metricId, uint256 value) external onlyOwner {
        metrics[metricId].latestValue      = value;
        metrics[metricId].twap             = value;
        metrics[metricId].lastUpdated      = uint64(block.timestamp);
        emergencyOverrideActive[metricId]  = true;
        emit EmergencyOverride(metricId, value);
    }

    function getMetricTWAP(bytes32 metricId, uint256 /* window */) external view returns (uint256) {
        return metrics[metricId].twap;
    }

    function isMetricFresh(bytes32 metricId) external view returns (bool) {
        return block.timestamp - metrics[metricId].lastUpdated <= MAX_METRIC_AGE;
    }

    function getMetric(bytes32 metricId) external view returns (AggregatedMetric memory) {
        AggregatedMetric storage m = metrics[metricId];
        if (block.timestamp - m.lastUpdated > MAX_METRIC_AGE) revert MetricTooOld();
        return m;
    }

    /// @dev [audit-v3] Returns the number of registered sources so partners can verify readiness
    function getRegisteredSourceCount() external view returns (uint256 count) {
        for (uint256 i = 0; i < sourceList.length; i++) {
            if (sources[sourceList[i]].registered) count++;
        }
    }
}
