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

import "@openzeppelin/contracts/utils/Strings.sol";
import "./IERC8004.sol";

/// @dev The subset of the LIVE, deployed AereAgentDID (chain 2800, 0xce64…22C5) this adapter reads.
///      The DID is the Aere-native agent identity: an agent whose ROOT authority is a Falcon key in
///      AerePQCKeyRegistry. This adapter never writes to the DID and never redeploys it.
interface IAereAgentDIDIdentity {
    function agentExists(uint256 rootKeyId) external view returns (bool);

    function getAgent(uint256 rootKeyId)
        external
        view
        returns (address controller, uint64 createdBlock, uint64 sessionNonce, uint256 sessionCountForAgent);

    function keyRegistry() external view returns (address);
}

/// @dev The one AerePQCKeyRegistry read used to resolve an agent's CURRENT controller.
interface IAerePQCKeyRegistryOwner {
    function ownerOf(uint256 keyId) external view returns (address);
}

/**
 * @title AereIdentityRegistry8004, an ERC-8004 Identity adapter over the deployed AereAgentDID
 *
 * @notice Exposes the ERC-8004 Identity registry surface (register / resolve an agent with a DID
 *         plus an AgentCard metadata URI) over Aere Network's ALREADY-DEPLOYED agent identity
 *         primitive, AereAgentDID. It does NOT redeploy or modify the live DID: it reads the live
 *         DID for agent existence and control, and stores only the ERC-8004 metadata (domain +
 *         AgentCard URI) that the DID itself does not carry.
 *
 *         IDENTITY ANCHOR. The ERC-8004 agentId IS the AereAgentDID agent id, which is the Falcon
 *         ROOT key id in AerePQCKeyRegistry (agentId == rootKeyId). So an ERC-8004 resolve is a
 *         direct, honest pass-through to the live DID: the agent's on-chain root of authority is a
 *         quantum-durable Falcon key, not a bare secp256k1 address. The DID string is
 *         "did:aere:<chainId>:<agentId>".
 *
 *         REGISTER = BIND METADATA TO AN EXISTING DID AGENT. `register` does not mint a new
 *         identity (the identity already exists in the live DID). It binds an ERC-8004 AgentCard
 *         URI + domain to an existing DID agent, and is gated to that agent's CURRENT controller
 *         (the registry owner of the Falcon root key), so only the party who proved Falcon
 *         possession can publish the agent's ERC-8004 metadata.
 *
 * @dev    [VERIFY: confirm against the finalized ERC-8004 interface] The newer ERC-8004 Identity
 *         draft leans on an ERC-721 registration (register(tokenURI) minting a sequential id). Aere
 *         anchors the ERC-8004 identity to the pre-existing Falcon-rooted DID id instead of minting
 *         a fresh sequential id, because the trust root is the on-chain Falcon key, not a token.
 *         The resolve / getAgent / AgentCard-URI SEMANTICS match ERC-8004; the id-minting selector
 *         differs by design and is documented here.
 *
 *         HONEST SCOPE. Application-layer identity only. This does NOT change AERE consensus (blocks
 *         are still Besu QBFT with classical secp256k1 ECDSA). It holds no funds and has no admin.
 */
contract AereIdentityRegistry8004 is IERC8004Identity {
    using Strings for uint256;

    /// @notice The LIVE, deployed AereAgentDID this adapter presents an ERC-8004 face over.
    IAereAgentDIDIdentity public immutable DID;

    /// @notice The AerePQCKeyRegistry the DID roots into (read from the DID at construction). Used
    ///         only to resolve an agent's current controller (the registry owner of the root key).
    IAerePQCKeyRegistryOwner public immutable KEY_REGISTRY;

    struct Record {
        bool registered;
        string agentDomain;
        string agentCardURI;
        address boundController; // controller captured at registration (immutable per Falcon root key)
    }

    mapping(uint256 => Record) private _records; // agentId (== rootKeyId) => ERC-8004 metadata
    mapping(bytes32 => uint256) private _domainToAgentPlus1; // keccak(domain) => agentId + 1 (0 = unset)
    mapping(address => uint256[]) private _agentsOfAddress; // controller => agentIds it registered
    uint256[] private _registeredAgentIds; // append-only list of all registered agentIds

    error ZeroAddress();
    error UnknownAgent(uint256 agentId);
    error NotController(uint256 agentId);
    error AlreadyRegistered(uint256 agentId);
    error NotRegistered(uint256 agentId);
    error EmptyDomain();
    error DomainTaken(string agentDomain);

    constructor(address did) {
        if (did == address(0)) revert ZeroAddress();
        DID = IAereAgentDIDIdentity(did);
        address reg = IAereAgentDIDIdentity(did).keyRegistry();
        if (reg == address(0)) revert ZeroAddress();
        KEY_REGISTRY = IAerePQCKeyRegistryOwner(reg);
    }

    // ==========================================================================
    //                          Registration (metadata binding)
    // ==========================================================================

    /**
     * @notice Bind ERC-8004 metadata (domain + AgentCard URI) to an existing AereAgentDID agent.
     *         Gated to the agent's CURRENT controller (the registry owner of the Falcon root key).
     *         Does not mint a new identity: the identity already lives in the live DID.
     * @param agentId      the AereAgentDID agent id (== the Falcon root key id).
     * @param agentDomain  the agent's domain (non-empty, unique across this adapter).
     * @param agentCardURI the AgentCard / registration metadata document URI.
     */
    function register(uint256 agentId, string calldata agentDomain, string calldata agentCardURI) external {
        if (!DID.agentExists(agentId)) revert UnknownAgent(agentId);
        address controller = KEY_REGISTRY.ownerOf(agentId);
        if (controller == address(0) || msg.sender != controller) revert NotController(agentId);
        if (_records[agentId].registered) revert AlreadyRegistered(agentId);
        if (bytes(agentDomain).length == 0) revert EmptyDomain();

        bytes32 dk = keccak256(bytes(agentDomain));
        if (_domainToAgentPlus1[dk] != 0) revert DomainTaken(agentDomain);

        _records[agentId] =
            Record({registered: true, agentDomain: agentDomain, agentCardURI: agentCardURI, boundController: controller});
        _domainToAgentPlus1[dk] = agentId + 1;
        _agentsOfAddress[controller].push(agentId);
        _registeredAgentIds.push(agentId);

        emit AgentRegistered(agentId, agentDomain, agentCardURI, controller);
    }

    /**
     * @notice Update the ERC-8004 metadata for an already-registered agent. Controller-gated.
     *         Re-indexes the domain (the new domain must be free unless unchanged).
     */
    function updateAgent(uint256 agentId, string calldata newDomain, string calldata newCardURI) external {
        Record storage r = _records[agentId];
        if (!r.registered) revert NotRegistered(agentId);
        address controller = KEY_REGISTRY.ownerOf(agentId);
        if (controller == address(0) || msg.sender != controller) revert NotController(agentId);
        if (bytes(newDomain).length == 0) revert EmptyDomain();

        bytes32 oldKey = keccak256(bytes(r.agentDomain));
        bytes32 newKey = keccak256(bytes(newDomain));
        if (newKey != oldKey) {
            if (_domainToAgentPlus1[newKey] != 0) revert DomainTaken(newDomain);
            delete _domainToAgentPlus1[oldKey];
            _domainToAgentPlus1[newKey] = agentId + 1;
            r.agentDomain = newDomain;
        }
        r.agentCardURI = newCardURI;

        emit AgentUpdated(agentId, newDomain, newCardURI, controller);
    }

    // ==========================================================================
    //                                  Resolve
    // ==========================================================================

    /// @inheritdoc IERC8004Identity
    function getAgent(uint256 agentId) public view returns (AgentInfo memory) {
        Record storage r = _records[agentId];
        if (!r.registered) revert NotRegistered(agentId);
        return AgentInfo({
            agentId: agentId,
            did: didString(agentId),
            agentDomain: r.agentDomain,
            agentCardURI: r.agentCardURI,
            agentAddress: KEY_REGISTRY.ownerOf(agentId), // current controller, read live
            registered: true
        });
    }

    /// @inheritdoc IERC8004Identity
    function resolveByDomain(string calldata agentDomain) external view returns (AgentInfo memory) {
        uint256 plus1 = _domainToAgentPlus1[keccak256(bytes(agentDomain))];
        if (plus1 == 0) revert NotRegistered(0);
        return getAgent(plus1 - 1);
    }

    /// @inheritdoc IERC8004Identity
    /// @dev Returns the MOST RECENT agent registered by `agentAddress`. Use agentsOf() for the full set.
    function resolveByAddress(address agentAddress) external view returns (AgentInfo memory) {
        uint256[] storage ids = _agentsOfAddress[agentAddress];
        if (ids.length == 0) revert NotRegistered(0);
        return getAgent(ids[ids.length - 1]);
    }

    /// @inheritdoc IERC8004Identity
    function agentCount() external view returns (uint256) {
        return _registeredAgentIds.length;
    }

    // ==========================================================================
    //                                  Views
    // ==========================================================================

    /// @notice True iff ERC-8004 metadata has been bound for `agentId` (and the DID agent exists).
    function isRegistered(uint256 agentId) external view returns (bool) {
        return _records[agentId].registered && DID.agentExists(agentId);
    }

    /// @notice All agentIds registered by a controller (append-only; ids are stable Falcon roots).
    function agentsOf(address controller) external view returns (uint256[] memory) {
        return _agentsOfAddress[controller];
    }

    /// @notice The full list of registered agentIds (append-only).
    function registeredAgentIds() external view returns (uint256[] memory) {
        return _registeredAgentIds;
    }

    /// @notice The DID string for an agent: "did:aere:<chainId>:<agentId>".
    function didString(uint256 agentId) public view returns (string memory) {
        return string.concat("did:aere:", block.chainid.toString(), ":", agentId.toString());
    }
}
