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

// Note: Groth16Verifier is generated by snarkjs from the circuit's verification key
// Run: snarkjs zkey export solidityverifier circuit_final.zkey Groth16Verifier.sol
import "./Groth16Verifier.sol";

/**
 * @title ZKReceiptVerifier
 * @dev Contract for verifying zero-knowledge proofs for receipt attestation
 */
contract ZKReceiptVerifier is Groth16Verifier {
    
    // Events
    event ProofVerified(
        bytes32 indexed receiptHash,
        uint256 settlementAmount,
        uint256 timestamp,
        address indexed verifier
    );
    
    event ProofVerificationFailed(
        bytes32 indexed receiptHash,
        string reason
    );
    
    // Mapping to prevent double-spending
    mapping(bytes32 => bool) public verifiedReceipts;
    
    // Mapping for authorized verifiers
    mapping(address => bool) public authorizedVerifiers;
    
    // Address of the settlement contract
    address public settlementContract;
    
    // Circuit version
    uint256 public constant CIRCUIT_VERSION = 1;
    
    // Minimum settlement amount
    uint256 public constant MIN_SETTLEMENT_AMOUNT = 0;
    
    // Maximum timestamp drift (in seconds)
    uint256 public constant MAX_TIMESTAMP_DRIFT = 3600; // 1 hour
    
    modifier onlyAuthorized() {
        require(
            authorizedVerifiers[msg.sender] || 
            msg.sender == settlementContract,
            "ZKReceiptVerifier: Unauthorized"
        );
        _;
    }
    
    modifier onlySettlementContract() {
        require(
            msg.sender == settlementContract,
            "ZKReceiptVerifier: Only settlement contract"
        );
        _;
    }
    
    constructor() {
        // Deployer is initially authorized
        authorizedVerifiers[msg.sender] = true;
    }
    
    /**
     * @dev Verify a ZK proof for receipt attestation
     * @param a Proof parameter a (G1 point)
     * @param b Proof parameter b (G2 point)
     * @param c Proof parameter c (G1 point)
     * @param publicSignals Public signals [receiptHash] - matches receipt_simple circuit
     * @return valid Whether the proof is valid
     */
    function verifyReceiptProof(
        uint[2] calldata a,
        uint[2][2] calldata b,
        uint[2] calldata c,
        uint[1] calldata publicSignals
    ) external view returns (bool valid) {
        // Extract public signal - receiptHash only for SimpleReceipt circuit
        bytes32 receiptHash = bytes32(publicSignals[0]);
        
        // Validate receipt hash is not zero
        if (receiptHash == bytes32(0)) {
            return false;
        }
        
        // Verify the proof using Groth16 (inherited from Groth16Verifier)
        return _verifyProof(a, b, c, publicSignals);
    }
    
    /**
     * @dev Verify and record a proof for settlement
     * @param a Proof parameter a (G1 point)
     * @param b Proof parameter b (G2 point)
     * @param c Proof parameter c (G1 point)
     * @param publicSignals Public signals [receiptHash]
     * @param settlementAmount Amount to settle (passed separately, not in proof)
     * @return success Whether verification succeeded
     */
    function verifyAndRecord(
        uint[2] calldata a,
        uint[2][2] calldata b,
        uint[2] calldata c,
        uint[1] calldata publicSignals,
        uint256 settlementAmount
    ) external onlyAuthorized returns (bool success) {
        // Extract public signal
        bytes32 receiptHash = bytes32(publicSignals[0]);
        
        // Check if receipt already verified (prevent double-spend)
        if (verifiedReceipts[receiptHash]) {
            emit ProofVerificationFailed(receiptHash, "Receipt already verified");
            return false;
        }
        
        // Validate receipt hash
        if (receiptHash == bytes32(0)) {
            emit ProofVerificationFailed(receiptHash, "Invalid receipt hash");
            return false;
        }
        
        // Verify the proof
        bool valid = _verifyProof(a, b, c, publicSignals);
        
        if (valid) {
            // Mark as verified
            verifiedReceipts[receiptHash] = true;
            
            // Emit event with settlement amount
            emit ProofVerified(receiptHash, settlementAmount, block.timestamp, msg.sender);
            
            return true;
        } else {
            emit ProofVerificationFailed(receiptHash, "Invalid proof");
            return false;
        }
    }
    
    /**
     * @dev Internal proof verification - calls inherited Groth16 verifier
     * @param a Proof parameter a
     * @param b Proof parameter b
     * @param c Proof parameter c
     * @param publicSignals Public signals array
     * @return valid Whether the proof is valid
     */
    function _verifyProof(
        uint[2] calldata a,
        uint[2][2] calldata b,
        uint[2] calldata c,
        uint[1] calldata publicSignals
    ) internal view returns (bool valid) {
        // Convert to format expected by Groth16Verifier
        // The Groth16Verifier.verifyProof is generated by snarkjs
        return Groth16Verifier.verifyProof(a, b, c, publicSignals);
    }
    
    /**
     * @dev Set the settlement contract address
     * @param _settlementContract Address of the settlement contract
     */
    function setSettlementContract(address _settlementContract) external {
        require(authorizedVerifiers[msg.sender], "ZKReceiptVerifier: Unauthorized");
        settlementContract = _settlementContract;
    }
    
    /**
     * @dev Add an authorized verifier
     * @param verifier Address to authorize
     */
    function addAuthorizedVerifier(address verifier) external {
        require(authorizedVerifiers[msg.sender], "ZKReceiptVerifier: Unauthorized");
        authorizedVerifiers[verifier] = true;
    }
    
    /**
     * @dev Remove an authorized verifier
     * @param verifier Address to remove
     */
    function removeAuthorizedVerifier(address verifier) external {
        require(authorizedVerifiers[msg.sender], "ZKReceiptVerifier: Unauthorized");
        authorizedVerifiers[verifier] = false;
    }
    
    /**
     * @dev Check if a receipt has been verified
     * @param receiptHash Hash of the receipt
     * @return verified Whether the receipt has been verified
     */
    function isReceiptVerified(bytes32 receiptHash) external view returns (bool verified) {
        return verifiedReceipts[receiptHash];
    }
    
    /**
     * @dev Batch verify multiple proofs
     * @param proofs Array of proof data
     * @return results Array of verification results
     */
    function batchVerify(
        BatchProof[] calldata proofs
    ) external view returns (bool[] memory results) {
        results = new bool[](proofs.length);
        
        for (uint256 i = 0; i < proofs.length; i++) {
            results[i] = _verifyProof(
                proofs[i].a,
                proofs[i].b,
                proofs[i].c,
                proofs[i].publicSignals
            );
        }
    }
    
    // Struct for batch verification
    struct BatchProof {
        uint[2] a;
        uint[2][2] b;
        uint[2] c;
        uint[1] publicSignals;  // Matches SimpleReceipt circuit
    }
}
