// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "@openzeppelin/contracts/access/Ownable.sol";

/**
 * @title AereForensicEventRegistry — Forta-style detection bot output
 *                                     registry for Chain 2800
 * @notice Open registry where third-party detection bots (mirror of the
 *         Forta Network ecosystem) post structured alerts about
 *         suspicious on-chain activity. Bots register first via
 *         registerBot(); they then submit alert events tagged with a
 *         severity and an alert type. Consumer dApps (lending markets,
 *         settlement venues) query the registry to gate counterparty
 *         actions when a high-severity recent alert flags an address.
 *
 *         AERE Foundation operates a small initial bot fleet (anomalous-
 *         flow detection, sandwich-tx detection, sanctioned-flow
 *         detection); third-party operators are welcomed via
 *         permissionless self-registration.
 *
 *         No asset custody. No transaction interception. Read-only audit
 *         surface.
 *
 * @dev    LISTING + RANKING:
 *           - Each bot has an immutable owner-set stakeRequirement (default
 *             0 in Phase 1 — purely reputational). Phase 2 (Q2 2027) will
 *             require an AERE bond + Foundation gating for high-severity
 *             alerts.
 *           - Per-bot reputation accrues from confirmed alerts (Foundation
 *             confirms in Phase 1; community-staked in Phase 2).
 *
 *         ALERT TAXONOMY (initial; bots can extend):
 *           0 = unknown / catch-all
 *           1 = sanctioned-flow detected (counterparty matches Sanctions
 *               Registry)
 *           2 = sandwich-tx / MEV pattern
 *           3 = anomalous-flow (sudden TVL drain, unusual contract)
 *           4 = oracle deviation
 *           5 = bridge exploit pattern
 *           6 = phishing contract pattern
 *           7 = governance vote-buying pattern
 *           8 = honeypot pattern
 *           9 = rug-pull pattern
 *
 *         SEVERITY:
 *           1 = info
 *           2 = low
 *           3 = medium
 *           4 = high
 *           5 = critical
 */
contract AereForensicEventRegistry is Ownable {

    struct Bot {
        bool registered;
        bool active;
        address owner;
        string name;
        string metadataUri;       // ipfs / https pointer to bot metadata
        uint256 stakeRequirement;
        uint256 alertCount;
        uint256 confirmedCount;
    }

    struct Alert {
        uint256 botId;
        address subject;          // the address the alert is about
        uint8 alertType;
        uint8 severity;
        uint64 timestamp;
        string detailUri;         // off-chain pointer (e.g. ipfs) with full details
        bool confirmed;           // Foundation confirmation in Phase 1
    }

    /// @notice botId → Bot
    mapping(uint256 => Bot) public bots;
    uint256 public nextBotId;

    /// @notice alertId → Alert
    mapping(uint256 => Alert) public alerts;
    uint256 public nextAlertId;

    /// @notice subject → recent alert ids (most recent first; capped at
    ///         RECENT_ALERTS_CAP per address by the off-chain indexer).
    mapping(address => uint256[]) public subjectAlerts;

    /* --------------------------------- events ------------------------------- */

    event BotRegistered(uint256 indexed botId, address indexed owner, string name, string metadataUri);
    event BotUpdated(uint256 indexed botId, string newMetadataUri, bool active);
    event AlertEmitted(uint256 indexed alertId, uint256 indexed botId, address indexed subject, uint8 alertType, uint8 severity, string detailUri);
    event AlertConfirmed(uint256 indexed alertId, uint256 indexed botId);
    event AlertRejected(uint256 indexed alertId, uint256 indexed botId, string reason);

    /* --------------------------------- errors ------------------------------- */

    error NotBotOwner();
    error UnknownBot();
    error UnknownAlert();
    error BotInactive();
    error ZeroSubject();

    /* ----------------------------- bot registration ------------------------- */

    function registerBot(string calldata name, string calldata metadataUri) external returns (uint256 botId) {
        botId = nextBotId++;
        bots[botId] = Bot({
            registered: true,
            active: true,
            owner: msg.sender,
            name: name,
            metadataUri: metadataUri,
            stakeRequirement: 0,
            alertCount: 0,
            confirmedCount: 0
        });
        emit BotRegistered(botId, msg.sender, name, metadataUri);
    }

    function updateBot(uint256 botId, string calldata newMetadataUri, bool active) external {
        Bot storage b = bots[botId];
        if (!b.registered) revert UnknownBot();
        if (msg.sender != b.owner) revert NotBotOwner();
        b.metadataUri = newMetadataUri;
        b.active = active;
        emit BotUpdated(botId, newMetadataUri, active);
    }

    /* ----------------------------- alert emission --------------------------- */

    function emitAlert(
        uint256 botId,
        address subject,
        uint8 alertType,
        uint8 severity,
        string calldata detailUri
    ) external returns (uint256 alertId) {
        Bot storage b = bots[botId];
        if (!b.registered) revert UnknownBot();
        if (!b.active) revert BotInactive();
        if (msg.sender != b.owner) revert NotBotOwner();
        if (subject == address(0)) revert ZeroSubject();

        alertId = nextAlertId++;
        alerts[alertId] = Alert({
            botId: botId,
            subject: subject,
            alertType: alertType,
            severity: severity,
            timestamp: uint64(block.timestamp),
            detailUri: detailUri,
            confirmed: false
        });
        subjectAlerts[subject].push(alertId);
        b.alertCount++;
        emit AlertEmitted(alertId, botId, subject, alertType, severity, detailUri);
    }

    /* ------------------------------- confirmation --------------------------- */

    /// @notice Phase 1: Foundation reviews + confirms valid alerts. Phase 2
    ///         replaces with staked-community confirmation.
    function confirmAlert(uint256 alertId) external onlyOwner {
        Alert storage a = alerts[alertId];
        if (a.timestamp == 0) revert UnknownAlert();
        if (a.confirmed) return;
        a.confirmed = true;
        bots[a.botId].confirmedCount++;
        emit AlertConfirmed(alertId, a.botId);
    }

    function rejectAlert(uint256 alertId, string calldata reason) external onlyOwner {
        Alert storage a = alerts[alertId];
        if (a.timestamp == 0) revert UnknownAlert();
        emit AlertRejected(alertId, a.botId, reason);
    }

    /* ----------------------------------- views ------------------------------ */

    /// @notice ROUND-3 FIX: cap maximum iterations to prevent griefable DoS
    ///         when an attacker spams a target subject with alerts. Consumers
    ///         (lending markets, etc.) that query this view in a transaction
    ///         path would otherwise be reverted by an attacker who emitted
    ///         millions of low-severity alerts against the subject.
    uint256 public constant MAX_RECENT_ITERATIONS = 256;

    /// @notice Returns the count of CONFIRMED alerts of `minSeverity` or
    ///         higher emitted within the last `windowSeconds` against the
    ///         given subject. Bounded at MAX_RECENT_ITERATIONS — for subjects
    ///         with > 256 recent alerts, only the most-recent 256 are
    ///         counted (still a useful signal; spam pushes older legit
    ///         alerts out of the window).
    function recentConfirmedAlertCount(
        address subject,
        uint8 minSeverity,
        uint256 windowSeconds
    ) external view returns (uint256 count) {
        uint256[] storage ids = subjectAlerts[subject];
        uint256 cutoff = block.timestamp > windowSeconds ? block.timestamp - windowSeconds : 0;
        uint256 iter = 0;
        for (uint256 i = ids.length; i > 0 && iter < MAX_RECENT_ITERATIONS; i--) {
            Alert storage a = alerts[ids[i - 1]];
            if (a.timestamp < cutoff) break;
            if (a.confirmed && a.severity >= minSeverity) count++;
            unchecked { iter++; }
        }
    }

    function subjectAlertCount(address subject) external view returns (uint256) {
        return subjectAlerts[subject].length;
    }
}
