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

import {ISP1Verifier} from "./ISP1Verifier.sol";
import {IRiscZeroVerifier, Receipt as R0Receipt, ReceiptClaim, ReceiptClaimLib, ExitCode, SystemExitCode, Output, OutputLib, SystemState, SystemStateLib} from "./risczero/IRiscZeroVerifier.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

/// @title AereProofRegistry — multi-prover zk proof registry on AERE chain 2800
/// @notice Verifies proofs from SP1 (Succinct Labs) and RISC Zero zkVM, then records each
///         successful verification as a permanent on-chain attestation.
///
///         Architecture:
///           - Proofs route through the SP1VerifierGateway and RiscZeroVerifierRouter.
///             That means future prover versions (SP1 v7, RISC Zero v2.0+) plug in by
///             calling addRoute on the upstream gateway — no migration of this registry.
///           - Programs (a vkey for SP1 / imageId for RISC Zero) must be registered before
///             proofs of them can be recorded. Registration is permissionless: anyone can
///             register their own program with a human-readable name + URL. The Foundation
///             can disable malicious programs but cannot disable legitimate ones.
///           - Proof submission emits ProofVerified with the program key indexed, so
///             off-chain consumers (Bank28 KYC, dApp loyalty, exchange withdrawal proofs)
///             can subscribe to events filtered by their program of interest.
///
///         The verifyXOnly view functions provide free verification without recording,
///         for dApps that need the verdict but not a permanent attestation.
contract AereProofRegistry is Ownable {
    using ReceiptClaimLib for ReceiptClaim;

    /// @notice The SP1 verifier gateway (routes by selector → concrete SP1 verifier version)
    address public immutable sp1Gateway;

    /// @notice The RISC Zero verifier router (routes by selector → concrete R0 verifier)
    address public immutable risc0Router;

    /// @notice Program registry — vkey (SP1) or imageId (R0) → Program metadata
    enum ProverSystem { SP1, RiscZero }

    struct Program {
        bool enabled;
        ProverSystem system;
        string name;
        string url;
        address registrant;
        uint64 registeredAt;
    }
    mapping(bytes32 => Program) public programs;
    bytes32[] public programKeys;

    /// @notice Permanent proof record
    struct ProofRecord {
        address submitter;
        uint64 timestamp;
        uint64 blockNumber;
        bytes32 programKey;       // vkey or imageId
        bytes32 publicValuesDigest; // sha256 of public values / journalDigest
        string description;
        ProverSystem system;
    }
    mapping(uint256 => ProofRecord) public proofs;
    uint256 public proofCount;

    mapping(bytes32 => uint256[]) public programProofs;
    mapping(address => uint256[]) public submitterProofs;

    event ProgramRegistered(
        bytes32 indexed programKey,
        ProverSystem indexed system,
        address indexed registrant,
        string name,
        string url
    );
    event ProgramDisabled(bytes32 indexed programKey, ProverSystem indexed system);
    event ProofVerified(
        uint256 indexed id,
        address indexed submitter,
        bytes32 indexed programKey,
        ProverSystem system,
        bytes32 publicValuesDigest,
        string description,
        uint64 timestamp,
        uint64 blockNumber
    );

    error ProgramAlreadyRegistered();
    error ProgramNotApproved();
    error EmptyName();
    error EmptyDescription();

    constructor(address _sp1Gateway, address _risc0Router) Ownable() {
        require(_sp1Gateway != address(0), "AereProofRegistry: sp1Gateway=0");
        require(_risc0Router != address(0), "AereProofRegistry: risc0Router=0");
        sp1Gateway = _sp1Gateway;
        risc0Router = _risc0Router;
    }

    // ─── Program registration (permissionless) ────────────────────────────────

    function registerSP1Program(
        bytes32 vkey,
        string calldata name,
        string calldata url
    ) external {
        _register(vkey, ProverSystem.SP1, name, url);
    }

    function registerRiscZeroProgram(
        bytes32 imageId,
        string calldata name,
        string calldata url
    ) external {
        _register(imageId, ProverSystem.RiscZero, name, url);
    }

    function _register(
        bytes32 key,
        ProverSystem system,
        string calldata name,
        string calldata url
    ) internal {
        if (bytes(name).length == 0) revert EmptyName();
        if (programs[key].registeredAt != 0) revert ProgramAlreadyRegistered();
        programs[key] = Program({
            enabled: true,
            system: system,
            name: name,
            url: url,
            registrant: msg.sender,
            registeredAt: uint64(block.timestamp)
        });
        programKeys.push(key);
        emit ProgramRegistered(key, system, msg.sender, name, url);
    }

    /// @notice Owner (Foundation) can disable a program known to be malicious or compromised.
    ///         This does not retroactively invalidate already-recorded proofs.
    function disableProgram(bytes32 key) external onlyOwner {
        Program storage p = programs[key];
        require(p.registeredAt != 0, "not registered");
        require(p.enabled, "already disabled");
        p.enabled = false;
        emit ProgramDisabled(key, p.system);
    }

    // ─── Proof submission (permissionless once program registered) ────────────

    function submitSP1Proof(
        bytes32 vkey,
        bytes calldata publicValues,
        bytes calldata proofBytes,
        string calldata description
    ) external returns (uint256 id) {
        Program storage p = programs[vkey];
        if (!p.enabled || p.system != ProverSystem.SP1) revert ProgramNotApproved();
        if (bytes(description).length == 0) revert EmptyDescription();

        ISP1Verifier(sp1Gateway).verifyProof(vkey, publicValues, proofBytes);
        id = _record(vkey, sha256(publicValues), description, ProverSystem.SP1);
    }

    function submitRiscZeroProof(
        bytes32 imageId,
        bytes32 journalDigest,
        bytes calldata seal,
        string calldata description
    ) external returns (uint256 id) {
        Program storage p = programs[imageId];
        if (!p.enabled || p.system != ProverSystem.RiscZero) revert ProgramNotApproved();
        if (bytes(description).length == 0) revert EmptyDescription();

        IRiscZeroVerifier(risc0Router).verify(seal, imageId, journalDigest);
        id = _record(imageId, journalDigest, description, ProverSystem.RiscZero);
    }

    function _record(
        bytes32 key,
        bytes32 pvDigest,
        string calldata description,
        ProverSystem system
    ) internal returns (uint256 id) {
        id = proofCount++;
        proofs[id] = ProofRecord({
            submitter: msg.sender,
            timestamp: uint64(block.timestamp),
            blockNumber: uint64(block.number),
            programKey: key,
            publicValuesDigest: pvDigest,
            description: description,
            system: system
        });
        programProofs[key].push(id);
        submitterProofs[msg.sender].push(id);
        emit ProofVerified(
            id, msg.sender, key, system, pvDigest, description,
            uint64(block.timestamp), uint64(block.number)
        );
    }

    // ─── Free verification (view, no record) ──────────────────────────────────

    function verifySP1Only(
        bytes32 vkey,
        bytes calldata publicValues,
        bytes calldata proofBytes
    ) external view {
        ISP1Verifier(sp1Gateway).verifyProof(vkey, publicValues, proofBytes);
    }

    function verifyRiscZeroOnly(
        bytes32 imageId,
        bytes32 journalDigest,
        bytes calldata seal
    ) external view {
        IRiscZeroVerifier(risc0Router).verify(seal, imageId, journalDigest);
    }

    // ─── Views ────────────────────────────────────────────────────────────────

    function programCount() external view returns (uint256) {
        return programKeys.length;
    }

    function submitterProofCount(address submitter) external view returns (uint256) {
        return submitterProofs[submitter].length;
    }

    function programProofCount(bytes32 key) external view returns (uint256) {
        return programProofs[key].length;
    }
}
