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

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

/// @dev The subset of the LIVE, deployed AerePQCKeyRegistry (chain 2800, 0x1eCa...3691) this registry
///      uses to verify an issuer's POST-QUANTUM attestation. verifyWithKey routes to the live native
///      precompile for the key's scheme (0x0AE1 Falcon-512) and verifies the signature IN FULL
///      on-chain, fail-closed.
interface IAerePQCKeyRegistryTrust {
    function verifyWithKey(uint256 keyId, bytes32 message, bytes calldata signature) external view returns (bool);
    function statusOf(uint256 keyId) external view returns (uint8);
    function schemeOf(uint256 keyId) external view returns (uint8);
}

/**
 * @title AereTrustRegistry, a federated registry of ACCREDITED credential issuers for Aere Network
 *
 * @notice The trust root for Aere's institution-grade compliance passport. Compliance trust is NOT
 *         a single Foundation key: it is a FEDERATED SET of accredited issuers, each authorized to
 *         issue a defined set of credential types. This mirrors, in spirit, the eIDAS 2.0 / EUDI
 *         "trusted list" model, in which a supervisory body maintains a machine-readable list of
 *         qualified trust service providers and the services they are accredited for. Here the Aere
 *         Foundation is the trusted-list operator (the registry owner); each entry is an accredited
 *         issuer with a DID, an accreditation SCOPE (the credential types it may issue), a lifecycle
 *         STATUS, and an OPTIONAL post-quantum (Falcon-512) key reference so the issuer's attestations
 *         can be PQC-signed and verified on-chain via the live precompile 0x0AE1.
 *
 *         WHY POST-QUANTUM. Every accreditation stack in production today authenticates an issuer's
 *         attestation with a classical ECDSA / RSA signature, which a cryptographically relevant
 *         quantum computer forges. An Aere issuer additionally binds a Falcon-512 key here, so its
 *         attestations are verified with a NIST post-quantum signature scheme in full on-chain. That
 *         is the differentiator this registry exists to carry.
 *
 *         DESIGN PRINCIPLES.
 *           - FAIL-CLOSED. An unknown issuer, a suspended or revoked issuer, or a credential type
 *             outside an issuer's scope all resolve to NOT accredited. isAccredited never returns a
 *             false positive.
 *           - APPEND-ONLY HISTORY, NO SILENT REWRITE. Every status transition and every scope change
 *             is pushed to an append-only history. The current status is a pointer; the record of how
 *             it got there is immutable and fully auditable.
 *           - TERMINAL REVOCATION. Accreditation revocation is terminal (a revoked issuer can never
 *             be reinstated). Suspension is reversible.
 *
 * @dev    HONEST SCOPE. This is application-layer compliance tooling. It changes NOTHING about Aere
 *         consensus, which stays classical secp256k1 ECDSA QBFT. What is post-quantum here is the
 *         AUTHENTICITY of an issuer attestation, verified by the live precompile. This contract holds
 *         no funds. Whether any REAL institution is accredited, and whether such accreditation carries
 *         legal weight under eIDAS 2.0, is a partnership / regulatory matter that is external to this
 *         code and is NOT asserted by deploying it.
 *
 * @dev    No PII on chain. An issuer entry stores a DID (a public identifier, not personal data) and
 *         opaque bytes32 credential-type tags. Reason codes on status transitions are opaque bytes32.
 */
contract AereTrustRegistry is Ownable {
    /// @notice Falcon-512 scheme id in AerePQCKeyRegistry (live precompile 0x0AE1).
    uint8 public constant SCHEME_FALCON512 = 1;
    /// @notice AerePQCKeyRegistry.STATUS_ACTIVE (only an ACTIVE key is a current key).
    uint8 internal constant KEY_STATUS_ACTIVE = 1;
    /// @notice Sentinel for "no post-quantum key linked to this issuer".
    uint256 public constant NO_KEY = type(uint256).max;

    /// @notice Issuer accreditation lifecycle. None=never accredited; Active=may issue in scope;
    ///         Suspended=temporarily halted (reversible); Revoked=terminal.
    enum Status {
        None,
        Active,
        Suspended,
        Revoked
    }

    /// @notice The LIVE AerePQCKeyRegistry used to verify an issuer's Falcon-512 attestations.
    IAerePQCKeyRegistryTrust public immutable KEY_REGISTRY;

    struct Issuer {
        address controller; // the issuer id / controlling address
        Status status; // current lifecycle status (history is append-only, see below)
        uint256 pqcKeyId; // Falcon-512 keyId in KEY_REGISTRY, or NO_KEY
        uint64 accreditedAt; // block timestamp of first accreditation
        string did; // the issuer's did:aere DID (public identifier, not PII)
    }

    /// @notice One append-only record per status transition. No entry is ever rewritten.
    struct StatusEvent {
        Status status;
        uint64 blockNumber;
        uint64 timestamp;
        bytes32 reason; // opaque reason code (e.g. keccak256("audit-lapsed")); never PII
    }

    /// @notice One append-only record per scope change.
    struct ScopeEvent {
        bytes32 credentialType;
        bool granted; // true = granted, false = removed
        uint64 timestamp;
    }

    // issuerId is the index into _issuers. Index 0 is a reserved placeholder so issuerIdByAddress's
    // default 0 unambiguously means "no such issuer"; real issuers have issuerId >= 1.
    Issuer[] private _issuers;
    mapping(address => uint256) public issuerIdByAddress; // controller => issuerId (0 = none)
    mapping(uint256 => mapping(bytes32 => bool)) private _inScope; // issuerId => credentialType => granted
    mapping(uint256 => bytes32[]) private _scopeList; // issuerId => append-only granted-type list (may repeat across grant/revoke cycles)
    mapping(uint256 => StatusEvent[]) private _statusHistory; // issuerId => append-only status log
    mapping(uint256 => ScopeEvent[]) private _scopeHistory; // issuerId => append-only scope log

    event IssuerAccredited(uint256 indexed issuerId, address indexed controller, string did, uint256 pqcKeyId);
    event IssuerStatusChanged(uint256 indexed issuerId, Status indexed oldStatus, Status indexed newStatus, bytes32 reason);
    event IssuerScopeChanged(uint256 indexed issuerId, bytes32 indexed credentialType, bool granted);
    event IssuerPqcKeySet(uint256 indexed issuerId, uint256 oldKeyId, uint256 newKeyId);

    error ZeroAddress();
    error EmptyDid();
    error AlreadyAccredited(address controller);
    error UnknownIssuer(uint256 issuerId);
    error NotActive(uint256 issuerId);
    error NotSuspended(uint256 issuerId);
    error AlreadyRevoked(uint256 issuerId);
    error PqcKeyNotActiveFalcon(uint256 keyId);
    error ScopeAlreadyGranted(uint256 issuerId, bytes32 credentialType);
    error ScopeNotGranted(uint256 issuerId, bytes32 credentialType);

    constructor(address keyRegistry_) {
        if (keyRegistry_ == address(0)) revert ZeroAddress();
        KEY_REGISTRY = IAerePQCKeyRegistryTrust(keyRegistry_);
        // Reserve index 0 as an inert placeholder so issuerId 0 is never a real issuer.
        _issuers.push(Issuer({controller: address(0), status: Status.None, pqcKeyId: NO_KEY, accreditedAt: 0, did: ""}));
    }

    // ==========================================================================
    //                          Accreditation (owner)
    // ==========================================================================

    /**
     * @notice Accredit a new issuer with an initial scope and an optional Falcon-512 key reference.
     *         Owner-only (the Foundation as trusted-list operator). The controller address is the
     *         issuer id and the party that controls the linked credential-issuing keys.
     * @param controller   the issuer's controlling address (also its issuer id / address).
     * @param did          the issuer's did:aere DID (public identifier; not validated for full DID
     *                     syntax here, that is an off-chain concern, see [VERIFY] in the docs).
     * @param scopes       the credential types this issuer may issue (opaque bytes32 tags).
     * @param pqcKeyId     a Falcon-512 keyId in KEY_REGISTRY, or NO_KEY for no post-quantum key.
     * @return issuerId    the assigned issuer id (>= 1).
     */
    function accreditIssuer(address controller, string calldata did, bytes32[] calldata scopes, uint256 pqcKeyId)
        external
        onlyOwner
        returns (uint256 issuerId)
    {
        if (controller == address(0)) revert ZeroAddress();
        if (bytes(did).length == 0) revert EmptyDid();
        if (issuerIdByAddress[controller] != 0) revert AlreadyAccredited(controller);
        if (pqcKeyId != NO_KEY) _requireActiveFalconKey(pqcKeyId);

        issuerId = _issuers.length;
        _issuers.push(
            Issuer({
                controller: controller,
                status: Status.Active,
                pqcKeyId: pqcKeyId,
                accreditedAt: uint64(block.timestamp),
                did: did
            })
        );
        issuerIdByAddress[controller] = issuerId;

        for (uint256 i = 0; i < scopes.length; i++) {
            _grantScope(issuerId, scopes[i]);
        }

        _statusHistory[issuerId].push(
            StatusEvent({status: Status.Active, blockNumber: uint64(block.number), timestamp: uint64(block.timestamp), reason: bytes32(0)})
        );

        emit IssuerAccredited(issuerId, controller, did, pqcKeyId);
    }

    /// @notice Grant an additional credential type to an issuer's accreditation scope (owner-only).
    function grantScope(uint256 issuerId, bytes32 credentialType) external onlyOwner {
        _requireIssuer(issuerId);
        _grantScope(issuerId, credentialType);
    }

    /// @notice Remove a credential type from an issuer's scope (owner-only). Append-only history keeps
    ///         the record of the grant and the removal; the current scope simply no longer includes it.
    function revokeScope(uint256 issuerId, bytes32 credentialType) external onlyOwner {
        _requireIssuer(issuerId);
        if (!_inScope[issuerId][credentialType]) revert ScopeNotGranted(issuerId, credentialType);
        _inScope[issuerId][credentialType] = false;
        _scopeHistory[issuerId].push(ScopeEvent({credentialType: credentialType, granted: false, timestamp: uint64(block.timestamp)}));
        emit IssuerScopeChanged(issuerId, credentialType, false);
    }

    /// @notice Suspend an active issuer (reversible). All its credentials fail verification until
    ///         reinstated (fail-closed). Owner-only.
    function suspendIssuer(uint256 issuerId, bytes32 reason) external onlyOwner {
        _requireIssuer(issuerId);
        if (_issuers[issuerId].status != Status.Active) revert NotActive(issuerId);
        _transition(issuerId, Status.Suspended, reason);
    }

    /// @notice Reinstate a suspended issuer to Active (owner-only).
    function reinstateIssuer(uint256 issuerId, bytes32 reason) external onlyOwner {
        _requireIssuer(issuerId);
        if (_issuers[issuerId].status != Status.Suspended) revert NotSuspended(issuerId);
        _transition(issuerId, Status.Active, reason);
    }

    /// @notice Permanently revoke an issuer's accreditation (terminal, not reversible). Owner-only.
    function revokeIssuer(uint256 issuerId, bytes32 reason) external onlyOwner {
        _requireIssuer(issuerId);
        if (_issuers[issuerId].status == Status.Revoked) revert AlreadyRevoked(issuerId);
        _transition(issuerId, Status.Revoked, reason);
    }

    /// @notice Set or clear an issuer's Falcon-512 key reference (owner-only). Pass NO_KEY to clear.
    function setIssuerPqcKey(uint256 issuerId, uint256 pqcKeyId) external onlyOwner {
        _requireIssuer(issuerId);
        if (pqcKeyId != NO_KEY) _requireActiveFalconKey(pqcKeyId);
        uint256 old = _issuers[issuerId].pqcKeyId;
        _issuers[issuerId].pqcKeyId = pqcKeyId;
        emit IssuerPqcKeySet(issuerId, old, pqcKeyId);
    }

    // ==========================================================================
    //                     Accreditation + PQC verification (views)
    // ==========================================================================

    /// @notice True iff `issuerId` is ACTIVE and `credentialType` is currently in its scope.
    ///         Fail-closed for unknown / suspended / revoked issuers and out-of-scope types.
    function isAccredited(uint256 issuerId, bytes32 credentialType) external view returns (bool) {
        if (issuerId == 0 || issuerId >= _issuers.length) return false;
        if (_issuers[issuerId].status != Status.Active) return false;
        return _inScope[issuerId][credentialType];
    }

    /**
     * @notice Verify a POST-QUANTUM (Falcon-512) attestation by an issuer over a 32-byte message,
     *         via the live precompile 0x0AE1 through the AerePQCKeyRegistry. Fail-closed: returns
     *         false (never reverts) if the issuer is not Active, has no linked Falcon key, the key is
     *         not an ACTIVE Falcon-512 key, or the signature is invalid / tampered.
     * @param issuerId the accredited issuer.
     * @param message  the 32-byte message the issuer's Falcon key signed.
     * @param signature the Falcon-512 signature envelope (nonce(40) || esig).
     */
    function verifyIssuerSignature(uint256 issuerId, bytes32 message, bytes calldata signature)
        external
        view
        returns (bool)
    {
        if (issuerId == 0 || issuerId >= _issuers.length) return false;
        Issuer storage iss = _issuers[issuerId];
        if (iss.status != Status.Active) return false;
        uint256 keyId = iss.pqcKeyId;
        if (keyId == NO_KEY) return false;
        if (KEY_REGISTRY.statusOf(keyId) != KEY_STATUS_ACTIVE) return false;
        if (KEY_REGISTRY.schemeOf(keyId) != SCHEME_FALCON512) return false;
        return KEY_REGISTRY.verifyWithKey(keyId, message, signature);
    }

    // ==========================================================================
    //                                  Reads
    // ==========================================================================

    /// @notice Number of issuer ids ever assigned (issuerId runs 1..issuerCount, id 0 is reserved).
    function issuerCount() external view returns (uint256) {
        return _issuers.length - 1;
    }

    /// @notice The current lifecycle status of an issuer (Status.None for an unknown id).
    function issuerStatus(uint256 issuerId) external view returns (Status) {
        if (issuerId == 0 || issuerId >= _issuers.length) return Status.None;
        return _issuers[issuerId].status;
    }

    /// @notice Read an issuer entry. Reverts on an unknown id.
    function getIssuer(uint256 issuerId)
        external
        view
        returns (address controller, Status status, uint256 pqcKeyId, uint64 accreditedAt, string memory did)
    {
        _requireIssuer(issuerId);
        Issuer storage iss = _issuers[issuerId];
        return (iss.controller, iss.status, iss.pqcKeyId, iss.accreditedAt, iss.did);
    }

    /// @notice The Falcon-512 keyId linked to an issuer (NO_KEY if none / unknown issuer).
    function pqcKeyOf(uint256 issuerId) external view returns (uint256) {
        if (issuerId == 0 || issuerId >= _issuers.length) return NO_KEY;
        return _issuers[issuerId].pqcKeyId;
    }

    /// @notice The append-only status-history length for an issuer.
    function statusHistoryLength(uint256 issuerId) external view returns (uint256) {
        return _statusHistory[issuerId].length;
    }

    /// @notice Read one append-only status-history entry.
    function statusHistoryAt(uint256 issuerId, uint256 index) external view returns (StatusEvent memory) {
        return _statusHistory[issuerId][index];
    }

    /// @notice The append-only scope-history length for an issuer.
    function scopeHistoryLength(uint256 issuerId) external view returns (uint256) {
        return _scopeHistory[issuerId].length;
    }

    /// @notice Read one append-only scope-history entry.
    function scopeHistoryAt(uint256 issuerId, uint256 index) external view returns (ScopeEvent memory) {
        return _scopeHistory[issuerId][index];
    }

    // ==========================================================================
    //                                  Internal
    // ==========================================================================

    function _requireIssuer(uint256 issuerId) internal view {
        if (issuerId == 0 || issuerId >= _issuers.length) revert UnknownIssuer(issuerId);
    }

    function _requireActiveFalconKey(uint256 keyId) internal view {
        if (KEY_REGISTRY.statusOf(keyId) != KEY_STATUS_ACTIVE || KEY_REGISTRY.schemeOf(keyId) != SCHEME_FALCON512) {
            revert PqcKeyNotActiveFalcon(keyId);
        }
    }

    function _grantScope(uint256 issuerId, bytes32 credentialType) internal {
        if (_inScope[issuerId][credentialType]) revert ScopeAlreadyGranted(issuerId, credentialType);
        _inScope[issuerId][credentialType] = true;
        _scopeList[issuerId].push(credentialType);
        _scopeHistory[issuerId].push(ScopeEvent({credentialType: credentialType, granted: true, timestamp: uint64(block.timestamp)}));
        emit IssuerScopeChanged(issuerId, credentialType, true);
    }

    function _transition(uint256 issuerId, Status newStatus, bytes32 reason) internal {
        Status old = _issuers[issuerId].status;
        _issuers[issuerId].status = newStatus;
        _statusHistory[issuerId].push(
            StatusEvent({status: newStatus, blockNumber: uint64(block.number), timestamp: uint64(block.timestamp), reason: reason})
        );
        emit IssuerStatusChanged(issuerId, old, newStatus, reason);
    }
}
