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

import "./IVaultfireIdentity.sol";
import "./IVaultfireBonds.sol";
import "./IVaultfireReputation.sol";

/// @title AgentSafetyManager — Kill switch, safe mode, and emergency controls for AI agents
/// @author Vaultfire Protocol
/// @notice Implements EU AI Act / EU Data Act Article 36 compliant safety mechanisms:
///         - Kill switch: Freeze agent — all activity halted
///         - Safe mode: Agent can read/recommend but cannot execute
///         - Auto-triggers: Trust score thresholds trigger automatic safety actions
/// @dev All safety state changes are logged on-chain for regulatory audit trails.
contract AgentSafetyManager {
    // ═══════════════════════════════════════════════════
    // ENUMS & STRUCTS
    // ═══════════════════════════════════════════════════

    enum SafetyStatus { Active, SafeMode, Frozen }

    struct AgentSafetyState {
        SafetyStatus status;
        uint256 statusChangedAt;        // Timestamp of last status change
        address changedBy;              // Who triggered the status change
        string reason;                  // Human-readable reason for status change
        uint256 autoTriggerCount;       // Times auto-triggered
        uint256 manualOverrideCount;    // Times manually overridden
        bool exists;                    // Whether agent has a safety record
    }

    struct SafetyEvent {
        address agent;
        SafetyStatus fromStatus;
        SafetyStatus toStatus;
        address triggeredBy;
        uint256 timestamp;
        string reason;
        bool isAutoTrigger;
    }

    // ═══════════════════════════════════════════════════
    // STATE
    // ═══════════════════════════════════════════════════

    IVaultfireIdentity public immutable identityRegistry;
    IVaultfireBonds public immutable bondsRegistry;
    IVaultfireReputation public immutable reputationRegistry;

    address public owner;

    // Safety thresholds (configurable)
    uint256 public safeModeThreshold = 30;   // Rep below this → auto safe mode
    uint256 public freezeThreshold = 10;      // Rep below this → auto freeze

    // Agent safety states
    mapping(address => AgentSafetyState) public agentSafety;

    // Safety event log (for audit trail)
    SafetyEvent[] public safetyLog;
    mapping(address => uint256[]) public agentSafetyEvents;

    // Authorized safety operators (can trigger safety actions)
    mapping(address => bool) public safetyOperators;

    uint256 public totalFrozenAgents;
    uint256 public totalSafeModeAgents;

    // ═══════════════════════════════════════════════════
    // EVENTS
    // ═══════════════════════════════════════════════════

    event AgentFrozen(address indexed agent, address indexed triggeredBy, string reason);
    event AgentSafeModeActivated(address indexed agent, address indexed triggeredBy, string reason);
    event AgentReactivated(address indexed agent, address indexed triggeredBy, string reason);
    event SafetyThresholdsUpdated(uint256 safeModeThreshold, uint256 freezeThreshold);
    event SafetyOperatorUpdated(address indexed operator, bool authorized);

    // ═══════════════════════════════════════════════════
    // MODIFIERS
    // ═══════════════════════════════════════════════════

    modifier onlyOwner() {
        require(msg.sender == owner, "AgentSafetyManager: caller is not owner");
        _;
    }

    modifier onlySafetyOperator() {
        require(
            msg.sender == owner || safetyOperators[msg.sender],
            "AgentSafetyManager: caller is not authorized"
        );
        _;
    }

    modifier onlyRegisteredAgent(address agent) {
        (string memory uri, bool active, , ) = identityRegistry.getAgent(agent);
        require(bytes(uri).length > 0, "AgentSafetyManager: agent not registered");
        _;
    }

    // ═══════════════════════════════════════════════════
    // CONSTRUCTOR
    // ═══════════════════════════════════════════════════

    constructor(
        address _identityRegistry,
        address _bondsRegistry,
        address _reputationRegistry
    ) {
        require(_identityRegistry != address(0), "AgentSafetyManager: zero identity address");
        require(_bondsRegistry != address(0), "AgentSafetyManager: zero bonds address");
        require(_reputationRegistry != address(0), "AgentSafetyManager: zero reputation address");

        identityRegistry = IVaultfireIdentity(_identityRegistry);
        bondsRegistry = IVaultfireBonds(_bondsRegistry);
        reputationRegistry = IVaultfireReputation(_reputationRegistry);
        owner = msg.sender;
    }

    // ═══════════════════════════════════════════════════
    // SAFETY CONTROLS
    // ═══════════════════════════════════════════════════

    /// @notice KILL SWITCH — Immediately freeze an agent (all activity halted)
    /// @param agent The agent to freeze
    /// @param reason Human-readable reason for audit trail
    function freezeAgent(
        address agent,
        string calldata reason
    ) external onlySafetyOperator onlyRegisteredAgent(agent) {
        _updateSafetyStatus(agent, SafetyStatus.Frozen, reason, false);
    }

    /// @notice Activate SAFE MODE — Agent can read/recommend but cannot execute
    /// @param agent The agent to put in safe mode
    /// @param reason Human-readable reason for audit trail
    function activateSafeMode(
        address agent,
        string calldata reason
    ) external onlySafetyOperator onlyRegisteredAgent(agent) {
        _updateSafetyStatus(agent, SafetyStatus.SafeMode, reason, false);
    }

    /// @notice Reactivate an agent from frozen or safe mode
    /// @param agent The agent to reactivate
    /// @param reason Human-readable reason for audit trail
    function reactivateAgent(
        address agent,
        string calldata reason
    ) external onlySafetyOperator onlyRegisteredAgent(agent) {
        AgentSafetyState storage state = agentSafety[agent];
        require(state.exists, "AgentSafetyManager: no safety record");
        require(state.status != SafetyStatus.Active, "AgentSafetyManager: already active");

        _updateSafetyStatus(agent, SafetyStatus.Active, reason, false);
    }

    /// @notice Check agent reputation and auto-trigger safety if below thresholds
    /// @dev Can be called by anyone — permissionless reputation check
    /// @param agent The agent to check
    function checkAndEnforceSafety(address agent) external onlyRegisteredAgent(agent) {
        (uint256 score, , ) = reputationRegistry.getReputation(agent);

        AgentSafetyState storage state = agentSafety[agent];

        if (score <= freezeThreshold) {
            if (!state.exists || state.status != SafetyStatus.Frozen) {
                _updateSafetyStatus(agent, SafetyStatus.Frozen, "Auto-freeze: reputation below threshold", true);
            }
        } else if (score <= safeModeThreshold) {
            if (!state.exists || state.status == SafetyStatus.Active) {
                _updateSafetyStatus(agent, SafetyStatus.SafeMode, "Auto-safe-mode: reputation below threshold", true);
            }
        }
    }

    // ═══════════════════════════════════════════════════
    // INTERNAL
    // ═══════════════════════════════════════════════════

    function _updateSafetyStatus(
        address agent,
        SafetyStatus newStatus,
        string memory reason,
        bool isAutoTrigger
    ) internal {
        AgentSafetyState storage state = agentSafety[agent];

        SafetyStatus oldStatus = state.exists ? state.status : SafetyStatus.Active;

        // Update counters
        if (state.exists) {
            if (state.status == SafetyStatus.Frozen) totalFrozenAgents--;
            if (state.status == SafetyStatus.SafeMode) totalSafeModeAgents--;
        }

        if (newStatus == SafetyStatus.Frozen) totalFrozenAgents++;
        if (newStatus == SafetyStatus.SafeMode) totalSafeModeAgents++;

        // Update state
        state.status = newStatus;
        state.statusChangedAt = block.timestamp;
        state.changedBy = msg.sender;
        state.reason = reason;
        state.exists = true;

        if (isAutoTrigger) {
            state.autoTriggerCount++;
        } else {
            state.manualOverrideCount++;
        }

        // Log event for audit trail
        uint256 eventIndex = safetyLog.length;
        safetyLog.push(SafetyEvent({
            agent: agent,
            fromStatus: oldStatus,
            toStatus: newStatus,
            triggeredBy: msg.sender,
            timestamp: block.timestamp,
            reason: reason,
            isAutoTrigger: isAutoTrigger
        }));
        agentSafetyEvents[agent].push(eventIndex);

        // Emit typed events
        if (newStatus == SafetyStatus.Frozen) {
            emit AgentFrozen(agent, msg.sender, reason);
        } else if (newStatus == SafetyStatus.SafeMode) {
            emit AgentSafeModeActivated(agent, msg.sender, reason);
        } else {
            emit AgentReactivated(agent, msg.sender, reason);
        }
    }

    // ═══════════════════════════════════════════════════
    // VIEW FUNCTIONS
    // ═══════════════════════════════════════════════════

    /// @notice Get the current safety status of an agent
    function getAgentStatus(address agent) external view returns (
        SafetyStatus status,
        uint256 statusChangedAt,
        address changedBy,
        string memory reason
    ) {
        AgentSafetyState storage state = agentSafety[agent];
        if (!state.exists) {
            return (SafetyStatus.Active, 0, address(0), "");
        }
        return (state.status, state.statusChangedAt, state.changedBy, state.reason);
    }

    /// @notice Check if an agent is allowed to execute actions
    function isAgentOperational(address agent) external view returns (bool) {
        AgentSafetyState storage state = agentSafety[agent];
        if (!state.exists) return true; // No safety record = operational
        return state.status == SafetyStatus.Active;
    }

    /// @notice Check if an agent is in safe mode (read-only)
    function isAgentInSafeMode(address agent) external view returns (bool) {
        AgentSafetyState storage state = agentSafety[agent];
        return state.exists && state.status == SafetyStatus.SafeMode;
    }

    /// @notice Check if an agent is frozen (kill switch active)
    function isAgentFrozen(address agent) external view returns (bool) {
        AgentSafetyState storage state = agentSafety[agent];
        return state.exists && state.status == SafetyStatus.Frozen;
    }

    /// @notice Get safety event indices for an agent
    function getAgentSafetyEvents(address agent) external view returns (uint256[] memory) {
        return agentSafetyEvents[agent];
    }

    /// @notice Get total safety log count (audit trail)
    function getSafetyLogCount() external view returns (uint256) {
        return safetyLog.length;
    }

    /// @notice Get global safety stats
    function getSafetyStats() external view returns (
        uint256 frozenCount,
        uint256 safeModeCount,
        uint256 totalEvents
    ) {
        return (totalFrozenAgents, totalSafeModeAgents, safetyLog.length);
    }

    // ═══════════════════════════════════════════════════
    // ADMIN
    // ═══════════════════════════════════════════════════

    /// @notice Update safety thresholds
    function setThresholds(uint256 _safeModeThreshold, uint256 _freezeThreshold) external onlyOwner {
        require(_safeModeThreshold > _freezeThreshold, "AgentSafetyManager: safe mode must be above freeze");
        require(_safeModeThreshold <= 100, "AgentSafetyManager: threshold out of range");

        safeModeThreshold = _safeModeThreshold;
        freezeThreshold = _freezeThreshold;
        emit SafetyThresholdsUpdated(_safeModeThreshold, _freezeThreshold);
    }

    /// @notice Add or remove a safety operator
    function setSafetyOperator(address operator, bool authorized) external onlyOwner {
        require(operator != address(0), "AgentSafetyManager: zero operator");
        safetyOperators[operator] = authorized;
        emit SafetyOperatorUpdated(operator, authorized);
    }

    /// @notice Transfer ownership
    function transferOwnership(address newOwner) external onlyOwner {
        require(newOwner != address(0), "AgentSafetyManager: zero owner");
        owner = newOwner;
    }
}
