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

import "@openzeppelin/contracts/access/Ownable.sol";
import {PQCGaslessOrder, IAerePQCOrderAuthorizer} from "../intents/IAerePQCOrder.sol";

/**
 * @title AerePQCOrderAuthorizer — a crypto-agility CONSUMER that authorizes ERC-7683
 *        cross-chain intents with post-quantum signatures (chain 2800)
 *
 * @notice This is the "PQC-order authorizer" for the AERE intent path. It turns a
 *         declarative ERC-7683 order plus a NIST post-quantum signature (Falcon-512/
 *         1024, ML-DSA-44, SLH-DSA-128s) into an on-chain accept/reject, verified by
 *         AERE's LIVE native PQC precompiles behind the governed AereCryptoRegistry
 *         (0xaE6fC596bb3eCcbf5c5D02D67B0Ef065b3Afbaa5).
 *
 *         Like {AereHybridAuthorizer}, it never pins a scheme. Every check first calls
 *         {AereCryptoRegistry.resolveActive} to follow the successor chain to the live
 *         ACTIVE row, then verifies through the registry's routed verifier. So when the
 *         Foundation retires a scheme on the REGISTRY with the standard swap
 *
 *              1. addAlgorithm(newScheme)        // register the successor, ACTIVE
 *              2. setSuccessor(oldId, newId)     // point old -> new
 *              3. setStatus(oldId, DEPRECATED|REVOKED)
 *
 *         every settler that routes orders through this authorizer starts accepting the
 *         NEW scheme and rejecting the OLD one on the very next block, with NO redeploy
 *         of this authorizer or of any consuming SpokePool.
 *
 * @dev    ORDER DIGEST. The 32-byte message the PQC key signs is a standard EIP-712
 *         digest, domain-separated to the CONSUMING SETTLER (its address is the
 *         verifyingContract), so one authorizer serves many pools and a signature for
 *         pool A never authorizes an order at pool B. The struct hash commits to the
 *         funder, the replay nonce, the chain, both deadlines, the requested algorithm
 *         id, the order-data type and payload hash, and the authorized public key. A
 *         solver's fill therefore settles only against an order a lattice/hash key
 *         actually authorized.
 *
 * @dev    HONEST SCOPE. Application-layer authorization only; it holds no funds (no
 *         payable function, no custody, no selfdestruct). It changes NOTHING about AERE
 *         consensus: blocks are still produced and signed by Besu QBFT validators with
 *         classical ECDSA / secp256k1. All post-quantum verification is delegated to the
 *         SAME live native precompiles the registry routes to. This is NOT post-quantum
 *         consensus. The token custody and ERC-7683 order lifecycle live in the settler
 *         (AereSpokePool); this contract only answers "is this order PQC-authorized?".
 *
 * @dev    POLICY. Only an ACTIVE signature scheme ever authorizes. {resolveActive}
 *         returns the first ACTIVE row on the successor chain, so a REVOKED id is never
 *         used (it is skipped for its ACTIVE successor, or, with no successor, the
 *         fail-closed check returns false); a DEPRECATED id with an ACTIVE successor
 *         routes to the successor; and a hash-only entry (e.g. SHAKE256) authorizes
 *         nothing because {AereCryptoRegistry.verify} refuses to route it.
 */
interface IAereCryptoRegistry {
    /// @notice Follow the successor chain to the first ACTIVE id. Reverts on unknown id,
    ///         a dead-end with no ACTIVE row, or a cycle.
    function resolveActive(uint256 algorithmId) external view returns (uint256);

    /// @notice Fail-closed routed signature verification (returns false, never reverts,
    ///         for unknown/hash-only/UNKNOWN/REVOKED/malformed inputs).
    function verify(uint256 algorithmId, bytes calldata pubKey, bytes32 messageHash, bytes calldata signature)
        external
        view
        returns (bool);

    /// @notice True iff `id` is a signature scheme safe to adopt for NEW work (ACTIVE).
    function isUsable(uint256 id) external view returns (bool);
}

contract AerePQCOrderAuthorizer is Ownable, IAerePQCOrderAuthorizer {
    // ==========================================================================
    //                              Immutable wiring
    // ==========================================================================

    /// @notice The governed crypto-agility registry this authorizer consumes.
    IAereCryptoRegistry public immutable registry;

    // ==========================================================================
    //                          EIP-712 digest constants
    // ==========================================================================

    bytes32 private constant EIP712_DOMAIN_TYPEHASH =
        keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
    bytes32 private constant DOMAIN_NAME = keccak256("AerePQCOrder");
    bytes32 private constant DOMAIN_VERSION = keccak256("1");

    /// @notice The order struct typehash. `orderDataHash` = keccak256(orderData) and
    ///         `pubKeyHash` = keccak256(pubKey) keep the digest fixed-size while binding
    ///         the full payload and the authorized key.
    bytes32 public constant PQC_ORDER_TYPEHASH = keccak256(
        "AerePQCOrder(address user,uint256 nonce,uint256 originChainId,uint32 openDeadline,uint32 fillDeadline,uint256 algorithmId,bytes32 orderDataType,bytes32 orderDataHash,bytes32 pubKeyHash)"
    );

    // ==========================================================================
    //                                  Storage
    // ==========================================================================

    /**
     * @notice The governed DEFAULT algorithm id used when an order sets `algorithmId == 0`.
     *         Owner-governed. It is re-resolved through {resolveActive} on every use, so
     *         it auto-follows a registry swap even before {migrate} crystallizes it.
     */
    uint256 public defaultAlgorithmId;

    // Authorization ledger (state-changing provenance surface; holds no value).
    /// @notice Number of successful {authorizeOrder} calls recorded.
    uint256 public authorizationCount;
    /// @notice The ACTIVE algorithm id used by the most recent successful authorization.
    uint256 public lastResolvedId;
    /// @notice The order digest of the most recent successful authorization.
    bytes32 public lastOrderDigest;

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

    event DefaultAlgorithmSet(uint256 indexed id);
    event DefaultMigrated(uint256 indexed oldId, uint256 indexed newId);
    event OrderAuthorized(
        address indexed settler, address indexed user, uint256 indexed resolvedId, bytes32 orderDigest, uint256 nonce
    );

    // ==========================================================================
    //                                  Errors
    // ==========================================================================

    error ZeroRegistry();
    error UnusableAlgorithm(uint256 id); // default id does not resolve to a usable ACTIVE signature scheme
    error NotCurrentDefault(uint256 got, uint256 current); // migrate(oldId) with oldId != defaultAlgorithmId
    error OrderAuthorizationFailed(uint256 requestedId, uint256 resolvedId);

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

    /**
     * @param registry_           the AereCryptoRegistry to route through (non-zero).
     * @param defaultAlgorithmId_ the initial governed default for algorithmId == 0 orders.
     *                            It MUST resolve, via the registry, to a usable ACTIVE
     *                            signature scheme now (fail-fast "safe default"), so the
     *                            contract can never be born pointing at an unusable or
     *                            hash-only default.
     */
    constructor(address registry_, uint256 defaultAlgorithmId_) {
        if (registry_ == address(0)) revert ZeroRegistry();
        registry = IAereCryptoRegistry(registry_);
        if (!_resolvesToUsable(defaultAlgorithmId_)) revert UnusableAlgorithm(defaultAlgorithmId_);
        defaultAlgorithmId = defaultAlgorithmId_;
        emit DefaultAlgorithmSet(defaultAlgorithmId_);
    }

    // ==========================================================================
    //                          Owner: default governance
    // ==========================================================================

    /**
     * @notice Set the governed default algorithm id (used for algorithmId == 0 orders).
     *         Owner only. The id must resolve to a usable ACTIVE signature scheme.
     */
    function setDefaultAlgorithm(uint256 id) external onlyOwner {
        if (!_resolvesToUsable(id)) revert UnusableAlgorithm(id);
        defaultAlgorithmId = id;
        emit DefaultAlgorithmSet(id);
    }

    /**
     * @notice Owner helper: crystallize the default onto the ACTIVE successor of `oldId`
     *         after a registry swap. `oldId` must equal the current {defaultAlgorithmId}.
     *         Because every check already re-resolves, this is an optimization/clarity
     *         step, not a correctness requirement. Reverts (from {resolveActive}) if the
     *         chain dead-ends or cycles.
     * @return newId the ACTIVE id the default now points at (== oldId if already ACTIVE).
     */
    function migrate(uint256 oldId) external onlyOwner returns (uint256 newId) {
        if (defaultAlgorithmId != oldId) revert NotCurrentDefault(oldId, defaultAlgorithmId);
        newId = registry.resolveActive(oldId);
        if (newId != oldId) {
            defaultAlgorithmId = newId;
            emit DefaultMigrated(oldId, newId);
        }
    }

    // ==========================================================================
    //                              Order digest
    // ==========================================================================

    /// @inheritdoc IAerePQCOrderAuthorizer
    function orderDigest(address settler, PQCGaslessOrder calldata order, bytes calldata pubKey)
        public
        view
        override
        returns (bytes32)
    {
        bytes32 structHash = keccak256(
            abi.encode(
                PQC_ORDER_TYPEHASH,
                order.user,
                order.nonce,
                order.originChainId,
                order.openDeadline,
                order.fillDeadline,
                order.algorithmId,
                order.orderDataType,
                keccak256(order.orderData),
                keccak256(pubKey)
            )
        );
        return keccak256(abi.encodePacked("\x19\x01", _domainSeparator(settler), structHash));
    }

    // ==========================================================================
    //                       Authorization (fail-closed view)
    // ==========================================================================

    /// @inheritdoc IAerePQCOrderAuthorizer
    function checkOrder(address settler, PQCGaslessOrder calldata order, bytes calldata pubKey, bytes calldata signature)
        public
        view
        override
        returns (bool)
    {
        uint256 requestedId = order.algorithmId == 0 ? defaultAlgorithmId : order.algorithmId;
        uint256 active;
        try registry.resolveActive(requestedId) returns (uint256 a) {
            active = a;
        } catch {
            return false; // unknown id, dead-end, or cycle -> not authorized
        }
        bytes32 digest = orderDigest(settler, order, pubKey);
        try registry.verify(active, pubKey, digest, signature) returns (bool ok) {
            return ok;
        } catch {
            return false;
        }
    }

    // ==========================================================================
    //                 Authorization (state-changing, recorded)
    // ==========================================================================

    /**
     * @notice State-changing authorization for a PQC order under the ACTIVE scheme
     *         reachable from `order.algorithmId` (0 => governed default). Reverts if the
     *         successor chain has no ACTIVE row (surfacing the registry reason) or
     *         {OrderAuthorizationFailed} if the routed verifier returns false. On success
     *         it bumps {authorizationCount} and emits {OrderAuthorized}. This is a
     *         provenance surface for standalone use; the consuming settler verifies with
     *         the gas-light fail-closed {checkOrder} view during open().
     * @return resolvedId the ACTIVE id the signature was verified under.
     */
    function authorizeOrder(
        address settler,
        PQCGaslessOrder calldata order,
        bytes calldata pubKey,
        bytes calldata signature
    ) external returns (uint256 resolvedId) {
        uint256 requestedId = order.algorithmId == 0 ? defaultAlgorithmId : order.algorithmId;
        resolvedId = registry.resolveActive(requestedId); // reverts on unknown/dead-end/cycle
        bytes32 digest = orderDigest(settler, order, pubKey);
        bool ok = registry.verify(resolvedId, pubKey, digest, signature);
        if (!ok) revert OrderAuthorizationFailed(requestedId, resolvedId);

        authorizationCount += 1;
        lastResolvedId = resolvedId;
        lastOrderDigest = digest;
        emit OrderAuthorized(settler, order.user, resolvedId, digest, order.nonce);
    }

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

    /// @dev The EIP-712 domain separator bound to `settler` as the verifyingContract, so
    ///      a PQC order authorized for one pool cannot authorize an order at another.
    function _domainSeparator(address settler) internal view returns (bytes32) {
        return keccak256(abi.encode(EIP712_DOMAIN_TYPEHASH, DOMAIN_NAME, DOMAIN_VERSION, block.chainid, settler));
    }

    /// @dev True iff `id` resolves (via the successor chain) to a usable ACTIVE signature
    ///      scheme. Fail-closed: a revert in resolveActive is treated as "not usable".
    function _resolvesToUsable(uint256 id) internal view returns (bool) {
        try registry.resolveActive(id) returns (uint256 active) {
            return registry.isUsable(active);
        } catch {
            return false;
        }
    }
}
