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

import "../contracts/PrivacyGuarantees.sol";
import "../contracts/MissionEnforcement.sol";

/**
 * @title ERC-8004 Identity Registry V3 for VaultFire
 * @notice Sovereign, portable AI agent identities with privacy guarantees
 * @dev V3 — Applies audit fixes: remove unbounded arrays, event-based enumeration,
 *      re-registration handling, URI validation, capabilitiesHash validation,
 *      agentType cap, admin forceDeactivate.
 *
 * **Mission Alignment:**
 * - No KYC: Wallet addresses only (privacy-first)
 * - Self-sovereign identity: Agents control their own metadata
 * - Portable reputation: Works across all ERC-8004 platforms
 * - Privacy over surveillance: ZK-compatible agent cards
 *
 * **V3 Changes (2026-02 Partner-Ready Audit):**
 * - HIGH-IR-01 & HIGH-IR-02: Remove registeredAgents and agentsByCapability arrays;
 *   replace with totalRegisteredAgents counter + indexed events for discovery
 * - MEDIUM-IR-01: Clear capabilitiesHash on deactivation; allow clean re-registration;
 *   add registrationCount per agent
 * - MEDIUM-IR-02: URI must start with "https://" or "ipfs://"; cap at 512 bytes
 * - LOW-IR-01: Reject zero capabilitiesHash
 * - LOW-IR-03: Cap agentType at 100 bytes
 * - Enhancement: owner role + forceDeactivate for emergency use
 *
 * @custom:security Inherits PrivacyGuarantees and MissionEnforcement
 * @custom:ethics No identity collection - wallet addresses only
 */
contract ERC8004IdentityRegistryV3 is PrivacyGuarantees, MissionEnforcement {

    // =========================================================================
    //  Owner role
    // =========================================================================

    address public owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }

    // =========================================================================
    //  Agent identity structs
    // =========================================================================

    struct AgentIdentity {
        address agentAddress;
        string agentURI;           // Off-chain agent card (JSON schema)
        uint256 registeredAt;
        bool active;
        string agentType;          // e.g., "AI Assistant", "Trading Bot", "Research Agent"
        bytes32 capabilitiesHash;  // Hash of capabilities for quick lookup
    }

    // Agent address => Identity
    mapping(address => AgentIdentity) public agents;

    /// @custom:audit-v3 HIGH-IR-01: Replace registeredAgents array with counter
    uint256 public totalRegisteredAgents;

    /// @custom:audit-v3 MEDIUM-IR-01: Per-agent registration count for re-registration tracking
    mapping(address => uint256) public registrationCount;

    // =========================================================================
    //  Events
    // =========================================================================

    event AgentRegistered(
        address indexed agentAddress,
        string agentURI,
        string agentType,
        bytes32 indexed capabilitiesHash,
        uint256 timestamp
    );

    event AgentUpdated(
        address indexed agentAddress,
        string newAgentURI,
        uint256 timestamp
    );

    event AgentDeactivated(
        address indexed agentAddress,
        uint256 timestamp
    );

    /// @custom:audit-v3 Enhancement: admin force-deactivation event
    event AgentForceDeactivated(
        address indexed agentAddress,
        address indexed admin,
        uint256 timestamp
    );

    // =========================================================================
    //  Constructor
    // =========================================================================

    constructor() {
        owner = msg.sender;
    }

    // =========================================================================
    //  Owner management
    // =========================================================================

    function transferOwnership(address newOwner) external onlyOwner {
        require(newOwner != address(0), "Invalid new owner");
        emit OwnershipTransferred(owner, newOwner);
        owner = newOwner;
    }

    // =========================================================================
    //  Internal helpers
    // =========================================================================

    /**
     * @dev MEDIUM-IR-02: Validate URI starts with "https://" or "ipfs://"
     */
    /// @custom:audit-v3 MEDIUM-IR-02: URI scheme validation helper
    function _validateURI(string calldata uri) internal pure {
        bytes memory b = bytes(uri);
        require(b.length > 0, "URI required");
        require(b.length <= 512, "URI too long (max 512 bytes)");

        // Check for "https://" (8 bytes) or "ipfs://" (7 bytes)
        bool validScheme = false;

        if (b.length >= 8) {
            // Check "https://"
            if (
                b[0] == 0x68 && b[1] == 0x74 && b[2] == 0x74 && b[3] == 0x70 &&
                b[4] == 0x73 && b[5] == 0x3A && b[6] == 0x2F && b[7] == 0x2F
            ) {
                validScheme = true;
            }
        }
        if (!validScheme && b.length >= 7) {
            // Check "ipfs://"
            if (
                b[0] == 0x69 && b[1] == 0x70 && b[2] == 0x66 && b[3] == 0x73 &&
                b[4] == 0x3A && b[5] == 0x2F && b[6] == 0x2F
            ) {
                validScheme = true;
            }
        }

        require(validScheme, "URI must start with https:// or ipfs://");
    }

    // =========================================================================
    //  Registration
    // =========================================================================

    /**
     * @notice Register an AI agent with ERC-8004 identity
     * @param agentURI Off-chain URI to agent card (JSON with capabilities, protocols, contact info)
     * @param agentType Human-readable agent type
     * @param capabilitiesHash Hash of agent capabilities for discovery
     * @dev Agent card schema should follow ERC-8004 standard format
     *
     * @dev MEDIUM-IR-01: Allows re-registration after deactivation. On re-registration
     *      the agent's record is updated cleanly and registrationCount is incremented.
     *      totalRegisteredAgents is only incremented on the FIRST registration.
     */
    /// @custom:audit-v3 HIGH-IR-01: totalRegisteredAgents replaces registeredAgents array
    /// @custom:audit-v3 MEDIUM-IR-01: Allow re-registration; track registrationCount
    /// @custom:audit-v3 MEDIUM-IR-02: URI validation
    /// @custom:audit-v3 LOW-IR-01: Reject zero capabilitiesHash
    /// @custom:audit-v3 LOW-IR-03: agentType capped at 100 bytes
    function registerAgent(
        string calldata agentURI,
        string calldata agentType,
        bytes32 capabilitiesHash
    ) external {
        require(bytes(agentType).length > 0, "Agent type required");

        /// @custom:audit-v3 LOW-IR-03: Cap agentType at 100 bytes
        require(bytes(agentType).length <= 100, "Agent type too long (max 100 bytes)");

        /// @custom:audit-v3 LOW-IR-01: Reject zero capabilitiesHash
        require(capabilitiesHash != bytes32(0), "Capabilities hash required");

        /// @custom:audit-v3 MEDIUM-IR-02: Validate URI scheme and length
        _validateURI(agentURI);

        AgentIdentity storage identity = agents[msg.sender];

        /// @custom:audit-v3 MEDIUM-IR-01: Prevent re-registration of ACTIVE agents;
        ///   allow re-registration after deactivation.
        require(!identity.active, "Agent already registered and active");

        bool isFirstRegistration = (registrationCount[msg.sender] == 0);

        // Clean update of the identity record (covers both fresh and re-registration)
        identity.agentAddress = msg.sender;
        identity.agentURI = agentURI;
        identity.registeredAt = block.timestamp;
        identity.active = true;
        identity.agentType = agentType;
        identity.capabilitiesHash = capabilitiesHash;

        registrationCount[msg.sender] += 1;

        /// @custom:audit-v3 HIGH-IR-01: Only increment counter on first registration
        if (isFirstRegistration) {
            totalRegisteredAgents += 1;
        }

        emit AgentRegistered(
            msg.sender,
            agentURI,
            agentType,
            capabilitiesHash,
            block.timestamp
        );
    }

    /**
     * @notice Update agent metadata URI
     * @param newAgentURI New URI to updated agent card
     */
    /// @custom:audit-v3 MEDIUM-IR-02: URI validation on update
    function updateAgentURI(string calldata newAgentURI) external {
        require(agents[msg.sender].active, "Agent not registered");

        /// @custom:audit-v3 MEDIUM-IR-02: Validate URI on update
        _validateURI(newAgentURI);

        agents[msg.sender].agentURI = newAgentURI;

        emit AgentUpdated(msg.sender, newAgentURI, block.timestamp);
    }

    /**
     * @notice Deactivate agent registration
     * @dev Agent can re-register by calling registerAgent() again after deactivation.
     *      MEDIUM-IR-01: capabilitiesHash is cleared on deactivation to prevent
     *      stale capability lookups from event-indexed discovery.
     */
    /// @custom:audit-v3 MEDIUM-IR-01: Clear capabilitiesHash on deactivation
    function deactivateAgent() external {
        require(agents[msg.sender].active, "Agent not registered");

        agents[msg.sender].active = false;

        /// @custom:audit-v3 MEDIUM-IR-01: Clear hash to prevent stale discovery
        agents[msg.sender].capabilitiesHash = bytes32(0);

        emit AgentDeactivated(msg.sender, block.timestamp);
    }

    /**
     * @notice Admin force-deactivate an agent for emergency use
     * @param agentAddress Agent to force-deactivate
     *
     * @dev Enhancement: owner role with emergency forceDeactivate capability.
     */
    /// @custom:audit-v3 Enhancement: admin forceDeactivate
    function forceDeactivate(address agentAddress) external onlyOwner {
        require(agentAddress != address(0), "Invalid agent address");
        require(agents[agentAddress].active, "Agent not active");

        agents[agentAddress].active = false;
        agents[agentAddress].capabilitiesHash = bytes32(0);

        emit AgentForceDeactivated(agentAddress, msg.sender, block.timestamp);
        emit AgentDeactivated(agentAddress, block.timestamp);
    }

    // =========================================================================
    //  View functions
    // =========================================================================

    /**
     * @notice Get agent identity details
     * @param agentAddress Address of the agent
     * @return agentURI URI to agent card
     * @return active Whether agent is currently active
     * @return agentType Type of agent
     * @return registeredAt Registration timestamp
     */
    function getAgent(address agentAddress)
        external
        view
        returns (
            string memory agentURI,
            bool active,
            string memory agentType,
            uint256 registeredAt
        )
    {
        AgentIdentity memory identity = agents[agentAddress];
        return (
            identity.agentURI,
            identity.active,
            identity.agentType,
            identity.registeredAt
        );
    }

    /**
     * @notice Get total number of registered agents
     * @return count Total registered agents (unique addresses, including inactive)
     *
     * @dev HIGH-IR-01: Returns the counter instead of a removed array's length.
     */
    /// @custom:audit-v3 HIGH-IR-01: Returns counter, no array
    function getTotalAgents() external view returns (uint256 count) {
        return totalRegisteredAgents;
    }

    /**
     * @notice Check if agent is registered and active
     * @param agentAddress Address to check
     * @return Whether agent is active
     */
    function isAgentActive(address agentAddress) external view returns (bool) {
        return agents[agentAddress].active;
    }

    /**
     * @notice Get an agent's registration count (0 = never registered)
     * @param agentAddress Address to check
     * @return Number of times the agent has registered (includes re-registrations)
     */
    function getRegistrationCount(address agentAddress) external view returns (uint256) {
        return registrationCount[agentAddress];
    }
}
