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

import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
import "../contracts/IStarkVerifier.sol";

/// @title DilithiumAttestorV3
/// @notice Records beliefs attested through hybrid STARK ZK proof + ECDSA signature verification.
/// @dev **PRODUCTION READY with STARK Integration:**
///      - When zkEnabled=false: Signature-only verification (V2 launch mode)
///      - When zkEnabled=true: STARK proof + signature verification (full ZK mode)
///
///      **STARK ZK System:**
///      - No trusted setup (aligns with "transparency with privacy")
///      - Post-quantum secure (future-proof)
///      - Scalable for large proof systems
///      - Proves: "I'm loyal, meet threshold, passed integrity check — without revealing how"
///
///      **Deployment Options:**
///      - V2 Launch: Deploy with zkEnabled=false (signature-only)
///      - Full ZK: Deploy BeliefAttestationVerifier, set zkEnabled=true
///
/// @custom:audit-v3 V3 fixes applied:
///   HIGH-1: Per-user attestedBeliefs mapping (bytes32 => mapping(address => bool))
///   HIGH-2: msg.sender bound into signed message to prevent replay across callers
///   CEI:    attestedBeliefs written BEFORE external ZK verifier call
///   LOW:    Re-attestation guard via require(!attestedBeliefs[beliefHash][msg.sender])
contract DilithiumAttestorV3 {
    using MessageHashUtils for bytes32;

    /// @notice Address whose signatures are considered valid origin attestations.
    address public immutable origin;

    /// @notice Flag to enable/disable ZK proof verification (for V2 launch flexibility).
    bool public immutable zkEnabled;

    /// @notice Address of the ZK verifier contract (if zkEnabled=true).
    /// @dev Set to address(0) if zkEnabled=false. Must be a valid STARK verifier if enabled.
    address public immutable verifierAddress;

    /// @notice Tracks belief hashes that were successfully attested, keyed per user.
    /// @dev HIGH-1 FIX: Per-user mapping prevents one user's attestation from
    ///      satisfying sovereignty checks for a different user.
    mapping(bytes32 => mapping(address => bool)) public attestedBeliefs;

    /// @notice Emitted whenever a belief hash is attested.
    /// @param beliefHash The hash representing the attested belief.
    /// @param prover The address that submitted the proof bundle.
    /// @param zkVerified True if ZK proof was verified, false if ZK was bypassed.
    event BeliefAttested(bytes32 beliefHash, address prover, bool zkVerified);

    /// @param _origin Address whose signatures are considered valid (governance multi-sig recommended).
    /// @param _zkEnabled Set to false for V2 launch (signature-only mode), true for full STARK ZK.
    /// @param _verifierAddress Address of STARK verifier (BeliefAttestationVerifier), or address(0) if zkEnabled=false.
    constructor(address _origin, bool _zkEnabled, address _verifierAddress) {
        require(_origin != address(0), "Invalid origin address");

        // If ZK is enabled, verifier must be provided and must be a valid contract
        if (_zkEnabled) {
            require(_verifierAddress != address(0), "ZK enabled but no verifier");
            // Verify it's a contract (has code)
            uint256 size;
            assembly {
                size := extcodesize(_verifierAddress)
            }
            require(size > 0, "Verifier address is not a contract");
        }

        origin = _origin;
        zkEnabled = _zkEnabled;
        verifierAddress = _verifierAddress;
    }

    /// @notice Attest a belief using STARK ZK proof and origin signature (or signature-only if ZK disabled).
    /// @dev The `zkProofBundle` is expected to be ABI-encoded as `(bytes proofData, bytes signature)`.
    ///
    ///      **If zkEnabled=true (Full ZK Mode):**
    ///      - proofData contains STARK proof that verifies (without revealing):
    ///        * Prover knows private belief matching beliefHash
    ///        * Belief meets protocol-defined threshold (80% alignment)
    ///        * Belief is authentic (originated through behavior, not fraud)
    ///        * Optional: Belief forged through Vaultfire-approved paths (NS3, GitHub, etc.)
    ///      - STARK proof is verified via IStarkVerifier interface
    ///      - Origin signature still required for additional security layer
    ///
    ///      **If zkEnabled=false (Signature-Only Mode - V2 Launch):**
    ///      - proofData is ignored (can be empty bytes)
    ///      - Only origin signature is verified
    ///      - Faster, simpler, but no privacy guarantees
    ///
    ///      **CEI Order:**
    ///      1. Validate inputs (sig check)
    ///      2. Write state (attestedBeliefs)
    ///      3. External call (ZK verifier)
    ///
    /// @param beliefHash The hash of the belief being attested.
    /// @param zkProofBundle ABI-encoded as `(bytes starkProof, bytes originSignature)`.
    function attestBelief(bytes32 beliefHash, bytes calldata zkProofBundle) external {
        (bytes memory proofData, bytes memory originSignature) = abi.decode(
            zkProofBundle,
            (bytes, bytes)
        );

        // LOW FIX: Prevent re-attestation by the same caller for the same belief.
        // @custom:audit-v3 Per-user guard — does not block other users from attesting same belief.
        require(!attestedBeliefs[beliefHash][msg.sender], "Already attested");

        // SECURITY: Validate origin signature FIRST (cheap ~3k gas).
        // HIGH-2 FIX: msg.sender is bound into the signed digest so a valid signature
        // for Alice cannot be replayed by Bob to attest on Alice's behalf.
        bytes32 ethSigned = keccak256(abi.encodePacked(beliefHash, msg.sender)).toEthSignedMessageHash();
        require(ECDSA.recover(ethSigned, originSignature) == origin, "Origin sig mismatch");

        // CEI FIX: Write state BEFORE any external call to prevent reentrancy.
        // @custom:audit-v3 attestedBeliefs set before verifyZKProof external call.
        attestedBeliefs[beliefHash][msg.sender] = true;

        bool zkVerified = false;

        // Then verify STARK proof if ZK is enabled (expensive ~500k+ gas).
        // Only executed after signature is confirmed valid and state is already written.
        if (zkEnabled) {
            require(verifyZKProof(proofData, beliefHash, msg.sender), "STARK proof invalid");
            zkVerified = true;
        }
        // If zkEnabled=false, skip ZK verification (signature-only mode for V2 launch)

        emit BeliefAttested(beliefHash, msg.sender, zkVerified);
    }

    /// @notice Verifies a STARK ZK proof using the configured verifier contract.
    /// @dev This function is only called if zkEnabled=true.
    ///      For V2 launch with zkEnabled=false, this code path is never executed.
    ///
    ///      **STARK Verification Process:**
    ///      1. Constructs public inputs array: [beliefHash, proverAddress, epoch, moduleID]
    ///      2. Calls IStarkVerifier.verifyProof() on the configured verifier contract
    ///      3. Returns true only if STARK proof is cryptographically valid
    ///
    ///      **Public Inputs:**
    ///      - beliefHash: Hash of the belief being attested
    ///      - proverAddress: Address of the prover (msg.sender)
    ///      - epoch: Campaign/era identifier (set to 0 for now, extensible)
    ///      - moduleID: Vaultfire module ID (set to 0 for now, extensible)
    ///
    ///      **Private Inputs (proven without revealing):**
    ///      - Actual belief message or claim
    ///      - Signature proving origin
    ///      - Loyalty proof (GitHub push, NS3 login, tweet ID, onchain move, etc.)
    ///
    /// @param proof The STARK proof bytes (generated off-chain).
    /// @param beliefHash The public belief hash to verify against.
    /// @param proverAddress The address of the prover.
    /// @return True if the STARK proof is valid and all constraints are satisfied.
    function verifyZKProof(
        bytes memory proof,
        bytes32 beliefHash,
        address proverAddress
    ) internal returns (bool) {
        require(verifierAddress != address(0), "Verifier not configured");

        // Construct public inputs array for STARK verifier
        // Format: [beliefHash, proverAddress, epoch, moduleID]
        uint256[] memory publicInputs = new uint256[](4);
        publicInputs[0] = uint256(beliefHash);
        publicInputs[1] = uint256(uint160(proverAddress));
        publicInputs[2] = 0; // epoch (future extension for campaign locking)
        publicInputs[3] = 0; // moduleID (future extension for NS3/GitHub/etc.)

        // Call the STARK verifier contract
        IStarkVerifier verifier = IStarkVerifier(verifierAddress);
        return verifier.verifyProof(proof, publicInputs);
    }

    /// @notice Check whether a belief hash has been attested by a specific address.
    /// @dev HIGH-1 FIX: Takes both beliefHash and attester so sovereignty is per-user,
    ///      not global. A belief attested by Alice does NOT satisfy Bob's sovereignty check.
    /// @param beliefHash The hash in question.
    /// @param attester The address to check attestation for.
    /// @return True if the belief hash has been successfully attested by the given attester.
    function isBeliefSovereign(bytes32 beliefHash, address attester) external view returns (bool) {
        return attestedBeliefs[beliefHash][attester];
    }
}
