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

/**
 * @title AereBestExReceipt — MiCA Article 78 best-execution receipts on-chain
 * @notice EU MiCA Title V (CASP conduct) Article 78 requires every Crypto
 *         Asset Service Provider to demonstrate "best execution" — that the
 *         price, costs, speed, likelihood, size, nature and other relevant
 *         considerations led to the most favourable result for the client.
 *         Until now, this proof has lived in CASPs' private back-office logs.
 *         AERE turns it into an open, append-only on-chain record.
 *
 *         For every trade executed on behalf of an EU client, a registered
 *         CASP MUST mint a receipt here. The receipt commits to:
 *
 *           - which venue was used  (executedVenue)
 *           - the price and quantity actually filled
 *           - the alternative venues considered, with their best quotes
 *             at the time of decision (alternativesRoot — Merkle root)
 *           - the speed (millisecond timestamp at which the order was
 *             accepted vs the execution timestamp)
 *           - the protocol/SOR algorithm version that made the routing call
 *
 *         Anyone — clients, supervisors, journalists — can later verify by
 *         (a) reading the on-chain receipt and (b) reproducing the
 *         alternative-quote Merkle tree from the CASP's published snapshot.
 *
 * @dev    DESIGN CHOICES:
 *
 *           1. NOT an NFT. ERC-721 carries semantics (transfer, marketplace)
 *              that don't fit a regulatory artefact. Receipts are bound to
 *              the CASP that issued them; transfer/ownership is irrelevant.
 *              Each receipt is a struct in a sealed mapping.
 *
 *           2. CASP registry is owned by the Foundation but registration is
 *              by-reference: Foundation publishes the CASP's MiCA licence
 *              number + national competent authority + LEI. The Foundation
 *              CANNOT amend a receipt after issuance, only register new
 *              CASPs and pause-register a bad actor.
 *
 *           3. No self-policing of receipt content. The contract does NOT
 *              attempt to validate that the venue chosen actually was the
 *              best — only that the CASP COMMITTED to a specific decision
 *              with a specific information set. Off-chain auditors / clients
 *              / supervisors detect non-compliance by reading the open
 *              record. This is the same model as Chainlink: the oracle
 *              commits, the market detects mispricing.
 *
 *           4. Per-CASP rolling counter (receiptNonce[casp]) avoids global
 *              counter contention. Each CASP can issue receipts in parallel
 *              without coordinating.
 *
 *           5. Pause is at the *issuance* level. A paused CASP cannot mint
 *              new receipts but historical receipts remain queryable and
 *              verifiable forever. Compliance discipline, not data deletion.
 *
 * @dev    GAS BUDGET (target):
 *           - issueReceipt: ~95-105k gas (mostly cold storage SSTOREs).
 *           - At 0.5 gwei base × 100k gas = 50_000 gwei = 0.00005 AERE
 *             (~ tiny fraction of a cent at any plausible AERE price).
 *           - A CASP doing 10M trades/yr pays << €500/yr in receipt gas.
 *           - That cost is the chain's competitive moat versus Polygon /
 *             Arbitrum / Base — at MiCA scale it stays a rounding error.
 */
contract AereBestExReceipt {

    /* ================================ types ================================ */

    /// @notice Permitted classification for the receipt (matches MiCA Art 78).
    enum OrderType {
        Market,           // 0
        Limit,            // 1
        StopLoss,         // 2
        StopLimit,        // 3
        TWAP,             // 4
        VWAP,             // 5
        RFQ,              // 6
        Other             // 7
    }

    /// @notice Permitted client classification under MiCA / MiFID II.
    enum ClientClass {
        Retail,           // 0  — MiCA Title V conduct rules fully apply
        Professional,     // 1  — opt-out clients
        Eligible          // 2  — eligible counterparties
    }

    struct CaspRegistration {
        bytes32 licenceRef;       // hash of (MiCA-licence-number || NCA-code || LEI)
        uint64  registeredAt;     // unix seconds
        bool    paused;           // emergency stop on issuance
        bool    exists;           // set true on registration; never unset
    }

    struct Receipt {
        // identity
        address casp;             // issuing CASP (== msg.sender at mint)
        bytes32 clientHash;       // keccak256(client-id || casp-internal-salt) — pseudonymous, MiCA Art 78(2)
        ClientClass clientClass;

        // order
        bytes32 orderId;          // CASP's internal order id (opaque); used by client to find this record
        OrderType orderType;
        bytes32 instrumentSymbol; // e.g. keccak256("BTC/EUR") — short stable identifier
        uint128 requestedQty;     // base asset, scaled to 18 dec
        uint128 executedQty;      // base asset, scaled to 18 dec
        uint128 executedPrice;    // quote asset per base, scaled to 18 dec

        // routing decision
        bytes32 executedVenue;    // keccak256(venue identifier — e.g. "Binance.com", "Coinbase Exchange")
        bytes32 alternativesRoot; // Merkle root of (venue, best-bid, best-ask, depth, timestamp) tuples
                                  //   considered at routing time; CASP publishes the leaves off-chain.
        bytes32 algorithmRef;     // hash of (SOR algo name || version || commit-sha) — pins which logic chose
        uint64  decisionTsMs;     // ms timestamp at which routing decision was made
        uint64  executionTsMs;    // ms timestamp at which fill confirmed

        // metadata pointer
        string  evidenceUri;      // ipfs://… or https://… — full alternatives JSON, fee schedule, etc.
                                  //   referenced read-only; chain stores only the root.
    }

    /* =============================== storage =============================== */

    address public immutable FOUNDATION;

    /// @notice CASP address → registration metadata. Once `exists`, never unset.
    mapping(address => CaspRegistration) public caspOf;

    /// @notice CASP → next nonce. receiptId = keccak256(casp, nonce).
    mapping(address => uint256) public receiptNonce;

    /// @notice global receiptId → Receipt. Append-only; never edited or deleted.
    mapping(bytes32 => Receipt) internal _receipts;

    /// @notice CASP → cumulative receipts issued.
    mapping(address => uint256) public issuedCount;

    /* ================================ events ================================ */

    event CaspRegistered(address indexed casp, bytes32 licenceRef);
    event CaspPaused(address indexed casp, bool paused);
    event ReceiptIssued(
        address indexed casp,
        bytes32 indexed receiptId,
        bytes32 indexed clientHash,
        bytes32 instrumentSymbol,
        bytes32 executedVenue,
        bytes32 alternativesRoot,
        uint128 executedQty,
        uint128 executedPrice,
        uint64  decisionTsMs,
        uint64  executionTsMs,
        bytes32 algorithmRef,
        string  evidenceUri
    );

    /* ================================ errors ================================ */

    error NotFoundation();
    error CaspExists();
    error CaspUnknown();
    error CaspIsPaused();
    error ReceiptExists();
    error ZeroLicence();
    error BadTimestamps();
    error BadQuantities();
    error BadAlternatives();

    /* =============================== modifiers ============================== */

    modifier onlyFoundation() {
        if (msg.sender != FOUNDATION) revert NotFoundation();
        _;
    }

    /* ============================== constructor ============================= */

    constructor(address foundation) {
        require(foundation != address(0), "zero-foundation");
        FOUNDATION = foundation;
    }

    /* ============================= registry ops ============================= */

    /// @notice Foundation registers a new CASP. licenceRef MUST hash an
    ///         actual MiCA licence + NCA + LEI off-chain (no on-chain PII).
    function registerCasp(address casp, bytes32 licenceRef) external onlyFoundation {
        if (casp == address(0)) revert CaspUnknown();
        if (licenceRef == bytes32(0)) revert ZeroLicence();
        if (caspOf[casp].exists) revert CaspExists();

        caspOf[casp] = CaspRegistration({
            licenceRef: licenceRef,
            registeredAt: uint64(block.timestamp),
            paused: false,
            exists: true
        });
        emit CaspRegistered(casp, licenceRef);
    }

    /// @notice Pause / unpause issuance for a CASP. Historical receipts
    ///         remain queryable and verifiable.
    function setCaspPaused(address casp, bool paused) external onlyFoundation {
        CaspRegistration storage r = caspOf[casp];
        if (!r.exists) revert CaspUnknown();
        r.paused = paused;
        emit CaspPaused(casp, paused);
    }

    /* ============================ issuance path ============================ */

    /**
     * @notice Mint a new best-execution receipt. msg.sender MUST be a
     *         registered, non-paused CASP. The receipt is bound to this
     *         CASP forever and cannot be amended.
     * @return receiptId = keccak256(abi.encode(msg.sender, nonce)).
     */
    function issueReceipt(Receipt calldata r) external returns (bytes32 receiptId) {
        CaspRegistration storage reg = caspOf[msg.sender];
        if (!reg.exists) revert CaspUnknown();
        if (reg.paused) revert CaspIsPaused();

        if (r.casp != msg.sender) revert CaspUnknown();
        if (r.executionTsMs < r.decisionTsMs) revert BadTimestamps();
        if (r.executedQty == 0 || r.executedPrice == 0) revert BadQuantities();
        if (r.executedQty > r.requestedQty) revert BadQuantities();
        if (r.alternativesRoot == bytes32(0)) revert BadAlternatives();
        if (r.executedVenue == bytes32(0)) revert BadAlternatives();

        uint256 nonce = receiptNonce[msg.sender]++;
        receiptId = keccak256(abi.encode(msg.sender, nonce));
        if (_receipts[receiptId].casp != address(0)) revert ReceiptExists();

        _receipts[receiptId] = r;
        issuedCount[msg.sender] = nonce + 1;

        emit ReceiptIssued(
            msg.sender,
            receiptId,
            r.clientHash,
            r.instrumentSymbol,
            r.executedVenue,
            r.alternativesRoot,
            r.executedQty,
            r.executedPrice,
            r.decisionTsMs,
            r.executionTsMs,
            r.algorithmRef,
            r.evidenceUri
        );
    }

    /* ================================ views ================================ */

    /// @notice Read a receipt by its id. Reverts not applied — caller checks
    ///         `r.casp == address(0)` for not-found.
    function getReceipt(bytes32 receiptId) external view returns (Receipt memory r) {
        return _receipts[receiptId];
    }

    /// @notice Deterministic receipt id for the next mint by `casp`.
    ///         Useful for relayer dry-runs.
    function nextReceiptId(address casp) external view returns (bytes32) {
        return keccak256(abi.encode(casp, receiptNonce[casp]));
    }

    /// @notice The decision-to-execution latency in milliseconds. One of the
    ///         "speed" inputs MiCA Art 78 names. Stored derivable for indexers.
    function executionLatencyMs(bytes32 receiptId) external view returns (uint64) {
        Receipt storage r = _receipts[receiptId];
        return r.executionTsMs - r.decisionTsMs;
    }

    /// @notice CASP is registered, non-paused, and has issued ≥ 1 receipt.
    ///         The minimum filter a client should apply when choosing a CASP.
    function caspIsActive(address casp) external view returns (bool) {
        CaspRegistration storage reg = caspOf[casp];
        return reg.exists && !reg.paused && issuedCount[casp] > 0;
    }
}
