#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "rln.h"

int main(int argc, char const *const argv[])
{
    printf("Creating RLN instance\n");

#define TREE_DEPTH 20

#ifdef STATELESS
    CResult_FFI_RLN_ptr_Vec_uint8_t ffi_rln_new_result = ffi_rln_new();
#else
#ifdef MULTI_MESSAGE_ID
    const char *zkey_path = "../resources/tree_depth_20/multi_message_id/max_out_4/rln_final.arkzkey";
    FILE *zkey_file = fopen(zkey_path, "rb");
    if (!zkey_file)
    {
        fprintf(stderr, "Failed to open zkey file: %s\n", zkey_path);
        return EXIT_FAILURE;
    }

    fseek(zkey_file, 0, SEEK_END);
    long zkey_size = ftell(zkey_file);
    fseek(zkey_file, 0, SEEK_SET);

    uint8_t *zkey_data = (uint8_t *)malloc(zkey_size);
    if (!zkey_data)
    {
        fprintf(stderr, "Failed to allocate memory for zkey\n");
        fclose(zkey_file);
        return EXIT_FAILURE;
    }

    fread(zkey_data, 1, zkey_size, zkey_file);
    fclose(zkey_file);

    Vec_uint8_t zkey_vec = {
        .ptr = zkey_data,
        .len = zkey_size,
        .cap = zkey_size};

    const char *graph_path = "../resources/tree_depth_20/multi_message_id/max_out_4/graph.bin";
    FILE *graph_file = fopen(graph_path, "rb");
    if (!graph_file)
    {
        fprintf(stderr, "Failed to open graph file: %s\n", graph_path);
        free(zkey_data);
        return EXIT_FAILURE;
    }

    fseek(graph_file, 0, SEEK_END);
    long graph_size = ftell(graph_file);
    fseek(graph_file, 0, SEEK_SET);

    uint8_t *graph_data = (uint8_t *)malloc(graph_size);
    if (!graph_data)
    {
        fprintf(stderr, "Failed to allocate memory for graph\n");
        fclose(graph_file);
        free(zkey_data);
        return EXIT_FAILURE;
    }

    fread(graph_data, 1, graph_size, graph_file);
    fclose(graph_file);

    Vec_uint8_t graph_vec = {
        .ptr = graph_data,
        .len = graph_size,
        .cap = graph_size};

    const char *config_path = "../resources/tree_depth_20/multi_message_id/max_out_4/config.json";
    CResult_FFI_RLN_ptr_Vec_uint8_t ffi_rln_new_result = ffi_rln_new_with_params(TREE_DEPTH, 4, &zkey_vec, &graph_vec, config_path);

    free(zkey_data);
    free(graph_data);
#else
    const char *config_path = "../resources/tree_depth_20/config.json";
    CResult_FFI_RLN_ptr_Vec_uint8_t ffi_rln_new_result = ffi_rln_new(TREE_DEPTH, config_path);
#endif
#endif

    if (!ffi_rln_new_result.ok)
    {
        fprintf(stderr, "Initial RLN instance creation error: %s\n", ffi_rln_new_result.err.ptr);
        ffi_c_string_free(ffi_rln_new_result.err);
        return EXIT_FAILURE;
    }

    FFI_RLN_t *rln = ffi_rln_new_result.ok;
    printf("  - RLN instance created successfully\n");

    size_t tree_depth_actual = ffi_rln_get_tree_depth(&rln);
    printf("  - circuit tree_depth = %zu\n", tree_depth_actual);
#ifdef MULTI_MESSAGE_ID
    size_t max_out = ffi_rln_get_max_out(&rln);
    printf("  - circuit max_out = %zu\n", max_out);
#endif

    printf("\nGenerating identity keys\n");
    Vec_CFr_t keys = ffi_key_gen();
    const CFr_t *identity_secret = ffi_vec_cfr_get(&keys, 0);
    const CFr_t *id_commitment = ffi_vec_cfr_get(&keys, 1);
    printf("  - identity generated successfully\n");

    Vec_uint8_t debug = ffi_cfr_debug(identity_secret);
    printf("  - identity_secret = %s\n", debug.ptr);
    ffi_c_string_free(debug);

    debug = ffi_cfr_debug(id_commitment);
    printf("  - id_commitment = %s\n", debug.ptr);
    ffi_c_string_free(debug);

    printf("\nCreating message limit\n");
    CFr_t *user_message_limit = ffi_uint_to_cfr(10);

    debug = ffi_cfr_debug(user_message_limit);
    printf("  - user_message_limit = %s\n", debug.ptr);
    ffi_c_string_free(debug);

    printf("\nComputing rate commitment\n");
    CFr_t *rate_commitment = ffi_poseidon_hash_pair(id_commitment, user_message_limit);

    debug = ffi_cfr_debug(rate_commitment);
    printf("  - rate_commitment = %s\n", debug.ptr);
    ffi_c_string_free(debug);

    printf("\nCFr serialization: CFr <-> bytes\n");
    Vec_uint8_t ser_rate_commitment = ffi_cfr_to_bytes_le(rate_commitment);

    debug = ffi_vec_u8_debug(&ser_rate_commitment);
    printf("  - serialized rate_commitment = %s\n", debug.ptr);
    ffi_c_string_free(debug);

    CResult_CFr_ptr_Vec_uint8_t deser_rate_commitment_result = ffi_bytes_le_to_cfr(&ser_rate_commitment);
    if (!deser_rate_commitment_result.ok)
    {
        fprintf(stderr, "Rate commitment deserialization error: %s\n", deser_rate_commitment_result.err.ptr);
        ffi_c_string_free(deser_rate_commitment_result.err);
        return EXIT_FAILURE;
    }
    CFr_t *deser_rate_commitment = deser_rate_commitment_result.ok;

    debug = ffi_cfr_debug(deser_rate_commitment);
    printf("  - deserialized rate_commitment = %s\n", debug.ptr);
    ffi_c_string_free(debug);

    ffi_vec_u8_free(ser_rate_commitment);
    ffi_cfr_free(deser_rate_commitment);

    printf("\nVec<CFr> serialization: Vec<CFr> <-> bytes\n");
    Vec_uint8_t ser_keys = ffi_vec_cfr_to_bytes_le(&keys);

    debug = ffi_vec_u8_debug(&ser_keys);
    printf("  - serialized keys = %s\n", debug.ptr);
    ffi_c_string_free(debug);

    CResult_Vec_CFr_Vec_uint8_t deser_keys_result = ffi_bytes_le_to_vec_cfr(&ser_keys);
    if (deser_keys_result.err.ptr)
    {
        fprintf(stderr, "Keys deserialization error: %s\n", deser_keys_result.err.ptr);
        ffi_c_string_free(deser_keys_result.err);
        return EXIT_FAILURE;
    }

    debug = ffi_vec_cfr_debug(&deser_keys_result.ok);
    printf("  - deserialized keys = %s\n", debug.ptr);
    ffi_c_string_free(debug);

    Vec_CFr_t deser_keys = deser_keys_result.ok;
    ffi_vec_cfr_free(deser_keys);

    ffi_vec_u8_free(ser_keys);

#ifdef STATELESS
    printf("\nBuilding Merkle path for stateless mode\n");
    CFr_t *default_leaf = ffi_cfr_zero();

    CFr_t *default_hashes[TREE_DEPTH - 1];
    default_hashes[0] = ffi_poseidon_hash_pair(default_leaf, default_leaf);
    for (size_t i = 1; i < TREE_DEPTH - 1; i++)
    {
        default_hashes[i] = ffi_poseidon_hash_pair(default_hashes[i - 1], default_hashes[i - 1]);
    }

    Vec_CFr_t path_elements = ffi_vec_cfr_new(TREE_DEPTH);
    ffi_vec_cfr_push(&path_elements, default_leaf);
    for (size_t i = 0; i < TREE_DEPTH - 1; i++)
    {
        ffi_vec_cfr_push(&path_elements, default_hashes[i]);
    }

    printf("\nVec<CFr> serialization: Vec<CFr> <-> bytes\n");
    Vec_uint8_t ser_path_elements = ffi_vec_cfr_to_bytes_le(&path_elements);

    debug = ffi_vec_u8_debug(&ser_path_elements);
    printf("  - serialized path_elements = %s\n", debug.ptr);
    ffi_c_string_free(debug);

    CResult_Vec_CFr_Vec_uint8_t deser_path_elements_result = ffi_bytes_le_to_vec_cfr(&ser_path_elements);
    if (deser_path_elements_result.err.ptr)
    {
        fprintf(stderr, "Path elements deserialization error: %s\n", deser_path_elements_result.err.ptr);
        ffi_c_string_free(deser_path_elements_result.err);
        return EXIT_FAILURE;
    }

    debug = ffi_vec_cfr_debug(&deser_path_elements_result.ok);
    printf("  - deserialized path_elements = %s\n", debug.ptr);
    ffi_c_string_free(debug);

    Vec_CFr_t deser_path_elements = deser_path_elements_result.ok;
    ffi_vec_cfr_free(deser_path_elements);

    ffi_vec_u8_free(ser_path_elements);

    uint8_t path_index_arr[TREE_DEPTH] = {0};
    Vec_uint8_t identity_path_index = {
        .ptr = path_index_arr,
        .len = TREE_DEPTH,
        .cap = TREE_DEPTH};

    printf("\nVec<uint8> serialization: Vec<uint8> <-> bytes\n");
    Vec_uint8_t ser_path_index = ffi_vec_u8_to_bytes_le(&identity_path_index);

    debug = ffi_vec_u8_debug(&ser_path_index);
    printf("  - serialized path_index = %s\n", debug.ptr);
    ffi_c_string_free(debug);

    CResult_Vec_uint8_Vec_uint8_t deser_path_index_result = ffi_bytes_le_to_vec_u8(&ser_path_index);
    if (deser_path_index_result.err.ptr)
    {
        fprintf(stderr, "Path index deserialization error: %s\n", deser_path_index_result.err.ptr);
        ffi_c_string_free(deser_path_index_result.err);
        return EXIT_FAILURE;
    }

    debug = ffi_vec_u8_debug(&deser_path_index_result.ok);
    printf("  - deserialized path_index = %s\n", debug.ptr);
    ffi_c_string_free(debug);

    Vec_uint8_t deser_path_index = deser_path_index_result.ok;
    ffi_vec_u8_free(deser_path_index);

    ffi_vec_u8_free(ser_path_index);

    printf("\nComputing Merkle root for stateless mode\n");
    printf("  - computing root for index 0 with rate_commitment\n");
    CFr_t *computed_root = ffi_poseidon_hash_pair(rate_commitment, default_leaf);
    for (size_t i = 1; i < TREE_DEPTH; i++)
    {
        CFr_t *next_root = ffi_poseidon_hash_pair(computed_root, default_hashes[i - 1]);
        ffi_cfr_free(computed_root);
        computed_root = next_root;
    }

    debug = ffi_cfr_debug(computed_root);
    printf("  - computed_root = %s\n", debug.ptr);
    ffi_c_string_free(debug);
#else
    printf("\nAdding rate_commitment to tree\n");
    CBoolResult_t set_err = ffi_set_next_leaf(&rln, rate_commitment);
    if (!set_err.ok)
    {
        fprintf(stderr, "Set next leaf error: %s\n", set_err.err.ptr);
        ffi_c_string_free(set_err.err);
        return EXIT_FAILURE;
    }

    size_t leaf_index = ffi_leaves_set(&rln) - 1;
    printf("  - added to tree at index %zu\n", leaf_index);

    printf("\nGetting Merkle proof\n");
    CResult_FFI_MerkleProof_ptr_Vec_uint8_t proof_result = ffi_get_merkle_proof(&rln, leaf_index);
    if (!proof_result.ok)
    {
        fprintf(stderr, "Get proof error: %s\n", proof_result.err.ptr);
        ffi_c_string_free(proof_result.err);
        return EXIT_FAILURE;
    }
    FFI_MerkleProof_t *merkle_proof = proof_result.ok;
    printf("  - proof obtained (depth: %zu)\n", merkle_proof->path_elements.len);
#endif

    printf("\nHashing signal\n");
    uint8_t signal[32] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    Vec_uint8_t signal_vec = {signal, 32, 32};
    CFr_t *x = ffi_hash_to_field_le(&signal_vec);

    debug = ffi_cfr_debug(x);
    printf("  - x = %s\n", debug.ptr);
    ffi_c_string_free(debug);

    printf("\nHashing epoch\n");
    const char *epoch_str = "test-epoch";
    Vec_uint8_t epoch_vec = {(uint8_t *)epoch_str, strlen(epoch_str), strlen(epoch_str)};
    CFr_t *epoch = ffi_hash_to_field_le(&epoch_vec);

    debug = ffi_cfr_debug(epoch);
    printf("  - epoch = %s\n", debug.ptr);
    ffi_c_string_free(debug);

    printf("\nHashing RLN identifier\n");
    const char *rln_id_str = "test-rln-identifier";
    Vec_uint8_t rln_id_vec = {(uint8_t *)rln_id_str, strlen(rln_id_str), strlen(rln_id_str)};
    CFr_t *rln_identifier = ffi_hash_to_field_le(&rln_id_vec);

    debug = ffi_cfr_debug(rln_identifier);
    printf("  - rln_identifier = %s\n", debug.ptr);
    ffi_c_string_free(debug);

    printf("\nComputing Poseidon hash for external nullifier\n");
    CFr_t *external_nullifier = ffi_poseidon_hash_pair(epoch, rln_identifier);

    debug = ffi_cfr_debug(external_nullifier);
    printf("  - external_nullifier = %s\n", debug.ptr);
    ffi_c_string_free(debug);

#ifndef MULTI_MESSAGE_ID
    printf("\nCreating message_id\n");
    CFr_t *message_id = ffi_uint_to_cfr(0);

    debug = ffi_cfr_debug(message_id);
    printf("  - message_id = %s\n", debug.ptr);
    ffi_c_string_free(debug);
#endif

#ifdef MULTI_MESSAGE_ID
    printf("\nCreating message_ids and selector_used (multi-message-id mode)\n");
    printf("  - using 2 out of %zu slots\n", max_out);

    Vec_CFr_t message_ids = ffi_vec_cfr_new(max_out);
    {
        CFr_t *tmp;
        tmp = ffi_uint_to_cfr(0);
        ffi_vec_cfr_push(&message_ids, tmp);
        ffi_cfr_free(tmp);
        tmp = ffi_uint_to_cfr(1);
        ffi_vec_cfr_push(&message_ids, tmp);
        ffi_cfr_free(tmp);
        for (size_t i = 2; i < max_out; i++)
        {
            tmp = ffi_cfr_zero();
            ffi_vec_cfr_push(&message_ids, tmp);
            ffi_cfr_free(tmp);
        }
    }

    bool *selector_arr = calloc(max_out, sizeof(bool));
    selector_arr[0] = true;
    selector_arr[1] = true;
    Vec_bool_t selector_used = {
        .ptr = selector_arr,
        .len = max_out,
        .cap = max_out};

    debug = ffi_vec_cfr_debug(&message_ids);
    printf("  - message_ids = %s\n", debug.ptr);
    ffi_c_string_free(debug);
#endif

    printf("\nCreating RLN Witness\n");
#ifdef STATELESS
    CResult_FFI_RLNWitnessInput_ptr_Vec_uint8_t witness_result =
#ifdef MULTI_MESSAGE_ID
        ffi_rln_witness_input_new(
            identity_secret,
            user_message_limit,
            &message_ids,
            &path_elements,
            &identity_path_index,
            x,
            external_nullifier,
            &selector_used);
#else
        ffi_rln_witness_input_new(
            identity_secret,
            user_message_limit,
            message_id,
            &path_elements,
            &identity_path_index,
            x,
            external_nullifier);
#endif

    if (!witness_result.ok)
    {
        fprintf(stderr, "RLN Witness creation error: %s\n", witness_result.err.ptr);
        ffi_c_string_free(witness_result.err);
        return EXIT_FAILURE;
    }
    FFI_RLNWitnessInput_t *witness = witness_result.ok;
    printf("  - RLN Witness created successfully\n");
#else
    CResult_FFI_RLNWitnessInput_ptr_Vec_uint8_t witness_result =
#ifdef MULTI_MESSAGE_ID
        ffi_rln_witness_input_new(
            identity_secret,
            user_message_limit,
            &message_ids,
            &merkle_proof->path_elements,
            &merkle_proof->path_index,
            x,
            external_nullifier,
            &selector_used);
#else
        ffi_rln_witness_input_new(
            identity_secret,
            user_message_limit,
            message_id,
            &merkle_proof->path_elements,
            &merkle_proof->path_index,
            x,
            external_nullifier);
#endif

    if (!witness_result.ok)
    {
        fprintf(stderr, "RLN Witness creation error: %s\n", witness_result.err.ptr);
        ffi_c_string_free(witness_result.err);
        return EXIT_FAILURE;
    }
    FFI_RLNWitnessInput_t *witness = witness_result.ok;
    printf("  - RLN Witness created successfully\n");
#endif

    printf("\nRLNWitnessInput serialization: RLNWitnessInput <-> bytes\n");
    CResult_Vec_uint8_Vec_uint8_t ser_witness_result = ffi_rln_witness_to_bytes_le(&witness);
    if (ser_witness_result.err.ptr)
    {
        fprintf(stderr, "Witness serialization error: %s\n", ser_witness_result.err.ptr);
        ffi_c_string_free(ser_witness_result.err);
        return EXIT_FAILURE;
    }
    Vec_uint8_t ser_witness = ser_witness_result.ok;

    debug = ffi_vec_u8_debug(&ser_witness);
    printf("  - serialized witness = %s\n", debug.ptr);
    ffi_c_string_free(debug);

    CResult_FFI_RLNWitnessInput_ptr_Vec_uint8_t deser_witness_result = ffi_bytes_le_to_rln_witness(&ser_witness);
    if (!deser_witness_result.ok)
    {
        fprintf(stderr, "Witness deserialization error: %s\n", deser_witness_result.err.ptr);
        ffi_c_string_free(deser_witness_result.err);
        return EXIT_FAILURE;
    }

    FFI_RLNWitnessInput_t *deser_witness = deser_witness_result.ok;
    printf("  - witness deserialized successfully\n");

    ffi_rln_witness_input_free(deser_witness);
    ffi_vec_u8_free(ser_witness);

    printf("\nGenerating RLN Proof\n");
    CResult_FFI_RLNProof_ptr_Vec_uint8_t proof_gen_result = ffi_generate_rln_proof(
        &rln,
        &witness);

    if (!proof_gen_result.ok)
    {
        fprintf(stderr, "Proof generation error: %s\n", proof_gen_result.err.ptr);
        ffi_c_string_free(proof_gen_result.err);
        return EXIT_FAILURE;
    }

    FFI_RLNProof_t *rln_proof = proof_gen_result.ok;
    printf("  - proof generated successfully\n");

    printf("\nGetting RLN Proof Values\n");
    FFI_RLNProofValues_t *proof_values = ffi_rln_proof_get_values(&rln_proof);

#ifdef MULTI_MESSAGE_ID
    CResult_Vec_CFr_Vec_uint8_t ys_result = ffi_rln_proof_values_get_ys(&proof_values);
    if (ys_result.err.ptr)
    {
        fprintf(stderr, "Get ys error: %s\n", ys_result.err.ptr);
        ffi_c_string_free(ys_result.err);
        return EXIT_FAILURE;
    }
    Vec_CFr_t ys = ys_result.ok;
    debug = ffi_vec_cfr_debug(&ys);
    printf("  - ys = %s\n", debug.ptr);
    ffi_c_string_free(debug);
    ffi_vec_cfr_free(ys);

    CResult_Vec_CFr_Vec_uint8_t nullifiers_result = ffi_rln_proof_values_get_nullifiers(&proof_values);
    if (nullifiers_result.err.ptr)
    {
        fprintf(stderr, "Get nullifiers error: %s\n", nullifiers_result.err.ptr);
        ffi_c_string_free(nullifiers_result.err);
        return EXIT_FAILURE;
    }
    Vec_CFr_t nullifiers = nullifiers_result.ok;
    debug = ffi_vec_cfr_debug(&nullifiers);
    printf("  - nullifiers = %s\n", debug.ptr);
    ffi_c_string_free(debug);
    ffi_vec_cfr_free(nullifiers);
#else
    CResult_CFr_ptr_Vec_uint8_t y_result = ffi_rln_proof_values_get_y(&proof_values);
    if (!y_result.ok)
    {
        fprintf(stderr, "Get y error: %s\n", y_result.err.ptr);
        ffi_c_string_free(y_result.err);
        return EXIT_FAILURE;
    }
    CFr_t *y = y_result.ok;
    debug = ffi_cfr_debug(y);
    printf("  - y = %s\n", debug.ptr);
    ffi_c_string_free(debug);
    ffi_cfr_free(y);

    CResult_CFr_ptr_Vec_uint8_t nullifier_result = ffi_rln_proof_values_get_nullifier(&proof_values);
    if (!nullifier_result.ok)
    {
        fprintf(stderr, "Get nullifier error: %s\n", nullifier_result.err.ptr);
        ffi_c_string_free(nullifier_result.err);
        return EXIT_FAILURE;
    }
    CFr_t *nullifier = nullifier_result.ok;
    debug = ffi_cfr_debug(nullifier);
    printf("  - nullifier = %s\n", debug.ptr);
    ffi_c_string_free(debug);
    ffi_cfr_free(nullifier);
#endif

    CFr_t *root = ffi_rln_proof_values_get_root(&proof_values);
    debug = ffi_cfr_debug(root);
    printf("  - root = %s\n", debug.ptr);
    ffi_c_string_free(debug);
    ffi_cfr_free(root);

    CFr_t *x_val = ffi_rln_proof_values_get_x(&proof_values);
    debug = ffi_cfr_debug(x_val);
    printf("  - x = %s\n", debug.ptr);
    ffi_c_string_free(debug);
    ffi_cfr_free(x_val);

    CFr_t *ext_nullifier = ffi_rln_proof_values_get_external_nullifier(&proof_values);
    debug = ffi_cfr_debug(ext_nullifier);
    printf("  - external_nullifier = %s\n", debug.ptr);
    ffi_c_string_free(debug);
    ffi_cfr_free(ext_nullifier);

    printf("\nRLNProof serialization: RLNProof <-> bytes\n");
    CResult_Vec_uint8_Vec_uint8_t ser_proof_result = ffi_rln_proof_to_bytes_le(&rln_proof);
    if (ser_proof_result.err.ptr)
    {
        fprintf(stderr, "Proof serialization error: %s\n", ser_proof_result.err.ptr);
        ffi_c_string_free(ser_proof_result.err);
        return EXIT_FAILURE;
    }
    Vec_uint8_t ser_proof = ser_proof_result.ok;

    debug = ffi_vec_u8_debug(&ser_proof);
    printf("  - serialized proof = %s\n", debug.ptr);
    ffi_c_string_free(debug);

    CResult_FFI_RLNProof_ptr_Vec_uint8_t deser_proof_result = ffi_bytes_le_to_rln_proof(&ser_proof);
    if (!deser_proof_result.ok)
    {
        fprintf(stderr, "Proof deserialization error: %s\n", deser_proof_result.err.ptr);
        ffi_c_string_free(deser_proof_result.err);
        return EXIT_FAILURE;
    }

    FFI_RLNProof_t *deser_proof = deser_proof_result.ok;
    printf("  - proof deserialized successfully\n");

    printf("\nRLNProofValues serialization: RLNProofValues <-> bytes\n");
    Vec_uint8_t ser_proof_values = ffi_rln_proof_values_to_bytes_le(&proof_values);

    debug = ffi_vec_u8_debug(&ser_proof_values);
    printf("  - serialized proof_values = %s\n", debug.ptr);
    ffi_c_string_free(debug);

    CResult_FFI_RLNProofValues_ptr_Vec_uint8_t deser_proof_values_result = ffi_bytes_le_to_rln_proof_values(&ser_proof_values);
    if (!deser_proof_values_result.ok)
    {
        fprintf(stderr, "RLN Proof Values deserialization error: %s\n", deser_proof_values_result.err.ptr);
        ffi_c_string_free(deser_proof_values_result.err);
        return EXIT_FAILURE;
    }
    FFI_RLNProofValues_t *deser_proof_values = deser_proof_values_result.ok;
    printf("  - proof_values deserialized successfully\n");

    CFr_t *deser_external_nullifier = ffi_rln_proof_values_get_external_nullifier(&deser_proof_values);
    debug = ffi_cfr_debug(deser_external_nullifier);
    printf("  - deserialized external_nullifier = %s\n", debug.ptr);
    ffi_c_string_free(debug);
    ffi_cfr_free(deser_external_nullifier);

    ffi_rln_proof_values_free(deser_proof_values);
    ffi_vec_u8_free(ser_proof_values);
    ffi_rln_proof_free(deser_proof);
    ffi_vec_u8_free(ser_proof);

    printf("\nVerifying Proof\n");
#ifdef STATELESS
    Vec_CFr_t roots = ffi_vec_cfr_from_cfr(computed_root);
    CBoolResult_t verify_err = ffi_verify_with_roots(&rln, &rln_proof, &roots, x);
#else
    CBoolResult_t verify_err = ffi_verify_rln_proof(&rln, &rln_proof, x);
#endif

    if (!verify_err.ok)
    {
        fprintf(stderr, "Proof verification error: %s\n", verify_err.err.ptr);
        ffi_c_string_free(verify_err.err);
        return EXIT_FAILURE;
    }

    printf("  - proof verified successfully\n");

    ffi_rln_proof_free(rln_proof);

    printf("\nGenerating partial proof from partial witness\n");

    FFI_RLNPartialWitnessInput_t *partial_witness = ffi_rln_witness_to_partial_witness(&witness);
    printf("  - partial witness created successfully\n");

    printf("\nRLNPartialWitnessInput serialization: RLNPartialWitnessInput <-> bytes\n");
    CResult_Vec_uint8_Vec_uint8_t ser_partial_witness_result = ffi_rln_partial_witness_to_bytes_le(&partial_witness);
    if (ser_partial_witness_result.err.ptr)
    {
        fprintf(stderr, "Partial witness serialization error: %s\n", ser_partial_witness_result.err.ptr);
        ffi_c_string_free(ser_partial_witness_result.err);
        return EXIT_FAILURE;
    }
    Vec_uint8_t ser_partial_witness = ser_partial_witness_result.ok;

    debug = ffi_vec_u8_debug(&ser_partial_witness);
    printf("  - serialized partial_witness = %s\n", debug.ptr);
    ffi_c_string_free(debug);

    CResult_FFI_RLNPartialWitnessInput_ptr_Vec_uint8_t deser_partial_witness_result = ffi_bytes_le_to_rln_partial_witness(&ser_partial_witness);
    if (!deser_partial_witness_result.ok)
    {
        fprintf(stderr, "Partial witness deserialization error: %s\n", deser_partial_witness_result.err.ptr);
        ffi_c_string_free(deser_partial_witness_result.err);
        return EXIT_FAILURE;
    }

    FFI_RLNPartialWitnessInput_t *deser_partial_witness = deser_partial_witness_result.ok;
    printf("  - partial_witness deserialized successfully\n");

    ffi_rln_partial_witness_input_free(deser_partial_witness);
    ffi_vec_u8_free(ser_partial_witness);

    printf("\nGenerating partial ZK proof\n");
    CResult_FFI_RLNPartialProof_ptr_Vec_uint8_t partial_proof_result = ffi_generate_partial_zk_proof(
        &rln,
        &partial_witness);

    if (!partial_proof_result.ok)
    {
        fprintf(stderr, "Partial proof generation error: %s\n", partial_proof_result.err.ptr);
        ffi_c_string_free(partial_proof_result.err);
        ffi_rln_partial_witness_input_free(partial_witness);
        return EXIT_FAILURE;
    }

    FFI_RLNPartialProof_t *partial_proof = partial_proof_result.ok;
    printf("  - partial proof generated successfully\n");

    printf("\nRLNPartialProof serialization: RLNPartialProof <-> bytes\n");
    CResult_Vec_uint8_Vec_uint8_t ser_partial_proof_result = ffi_rln_partial_proof_to_bytes_le(&partial_proof);
    if (ser_partial_proof_result.err.ptr)
    {
        fprintf(stderr, "Partial proof serialization error: %s\n", ser_partial_proof_result.err.ptr);
        ffi_c_string_free(ser_partial_proof_result.err);
        return EXIT_FAILURE;
    }
    Vec_uint8_t ser_partial_proof = ser_partial_proof_result.ok;

    debug = ffi_vec_u8_debug(&ser_partial_proof);
    printf("  - serialized partial_proof = %s\n", debug.ptr);
    ffi_c_string_free(debug);

    CResult_FFI_RLNPartialProof_ptr_Vec_uint8_t deser_partial_proof_result = ffi_bytes_le_to_rln_partial_proof(&ser_partial_proof);
    if (!deser_partial_proof_result.ok)
    {
        fprintf(stderr, "Partial proof deserialization error: %s\n", deser_partial_proof_result.err.ptr);
        ffi_c_string_free(deser_partial_proof_result.err);
        return EXIT_FAILURE;
    }

    FFI_RLNPartialProof_t *deser_partial_proof = deser_partial_proof_result.ok;
    printf("  - partial_proof deserialized successfully\n");

    ffi_rln_partial_proof_free(deser_partial_proof);
    ffi_vec_u8_free(ser_partial_proof);

    printf("\nFinishing proof with full witness\n");
    CResult_FFI_RLNProof_ptr_Vec_uint8_t finish_proof_result = ffi_finish_rln_proof(
        &rln,
        &partial_proof,
        &witness);

    if (!finish_proof_result.ok)
    {
        fprintf(stderr, "Finish proof error: %s\n", finish_proof_result.err.ptr);
        ffi_c_string_free(finish_proof_result.err);
        ffi_rln_partial_proof_free(partial_proof);
        ffi_rln_partial_witness_input_free(partial_witness);
        return EXIT_FAILURE;
    }

    FFI_RLNProof_t *partial_rln_proof = finish_proof_result.ok;
    printf("  - partial proof finished successfully\n");

    printf("\nGetting partial RLN Proof Values\n");
    FFI_RLNProofValues_t *partial_proof_values = ffi_rln_proof_get_values(&partial_rln_proof);

#ifdef MULTI_MESSAGE_ID
    CResult_Vec_CFr_Vec_uint8_t partial_ys_result = ffi_rln_proof_values_get_ys(&partial_proof_values);
    if (partial_ys_result.err.ptr)
    {
        fprintf(stderr, "Get partial ys error: %s\n", partial_ys_result.err.ptr);
        ffi_c_string_free(partial_ys_result.err);
        return EXIT_FAILURE;
    }
    Vec_CFr_t partial_ys = partial_ys_result.ok;
    debug = ffi_vec_cfr_debug(&partial_ys);
    printf("  - ys = %s\n", debug.ptr);
    ffi_c_string_free(debug);
    ffi_vec_cfr_free(partial_ys);

    CResult_Vec_CFr_Vec_uint8_t partial_nullifiers_result = ffi_rln_proof_values_get_nullifiers(&partial_proof_values);
    if (partial_nullifiers_result.err.ptr)
    {
        fprintf(stderr, "Get partial nullifiers error: %s\n", partial_nullifiers_result.err.ptr);
        ffi_c_string_free(partial_nullifiers_result.err);
        return EXIT_FAILURE;
    }
    Vec_CFr_t partial_nullifiers = partial_nullifiers_result.ok;
    debug = ffi_vec_cfr_debug(&partial_nullifiers);
    printf("  - nullifiers = %s\n", debug.ptr);
    ffi_c_string_free(debug);
    ffi_vec_cfr_free(partial_nullifiers);
#else
    CResult_CFr_ptr_Vec_uint8_t partial_y_result = ffi_rln_proof_values_get_y(&partial_proof_values);
    if (!partial_y_result.ok)
    {
        fprintf(stderr, "Get partial y error: %s\n", partial_y_result.err.ptr);
        ffi_c_string_free(partial_y_result.err);
        return EXIT_FAILURE;
    }
    CFr_t *partial_y = partial_y_result.ok;
    debug = ffi_cfr_debug(partial_y);
    printf("  - y = %s\n", debug.ptr);
    ffi_c_string_free(debug);
    ffi_cfr_free(partial_y);

    CResult_CFr_ptr_Vec_uint8_t partial_nullifier_result = ffi_rln_proof_values_get_nullifier(&partial_proof_values);
    if (!partial_nullifier_result.ok)
    {
        fprintf(stderr, "Get partial nullifier error: %s\n", partial_nullifier_result.err.ptr);
        ffi_c_string_free(partial_nullifier_result.err);
        return EXIT_FAILURE;
    }
    CFr_t *partial_nullifier = partial_nullifier_result.ok;
    debug = ffi_cfr_debug(partial_nullifier);
    printf("  - nullifier = %s\n", debug.ptr);
    ffi_c_string_free(debug);
    ffi_cfr_free(partial_nullifier);
#endif

    CFr_t *partial_root = ffi_rln_proof_values_get_root(&partial_proof_values);
    debug = ffi_cfr_debug(partial_root);
    printf("  - root = %s\n", debug.ptr);
    ffi_c_string_free(debug);
    ffi_cfr_free(partial_root);

    CFr_t *partial_x_val = ffi_rln_proof_values_get_x(&partial_proof_values);
    debug = ffi_cfr_debug(partial_x_val);
    printf("  - x = %s\n", debug.ptr);
    ffi_c_string_free(debug);
    ffi_cfr_free(partial_x_val);

    CFr_t *partial_ext_nullifier = ffi_rln_proof_values_get_external_nullifier(&partial_proof_values);
    debug = ffi_cfr_debug(partial_ext_nullifier);
    printf("  - external_nullifier = %s\n", debug.ptr);
    ffi_c_string_free(debug);
    ffi_cfr_free(partial_ext_nullifier);

    ffi_rln_proof_values_free(partial_proof_values);

    printf("\nVerifying partial-generated proof\n");
#ifdef STATELESS
    CBoolResult_t verify_partial_err = ffi_verify_with_roots(&rln, &partial_rln_proof, &roots, x);
#else
    CBoolResult_t verify_partial_err = ffi_verify_rln_proof(&rln, &partial_rln_proof, x);
#endif

    if (!verify_partial_err.ok)
    {
        fprintf(stderr, "Partial proof verification error: %s\n", verify_partial_err.err.ptr);
        ffi_c_string_free(verify_partial_err.err);
        ffi_rln_proof_free(partial_rln_proof);
        ffi_rln_partial_proof_free(partial_proof);
        ffi_rln_partial_witness_input_free(partial_witness);
        return EXIT_FAILURE;
    }

    printf("  - partial-generated proof verified successfully\n");

    ffi_rln_proof_free(partial_rln_proof);
    ffi_rln_partial_proof_free(partial_proof);
    ffi_rln_partial_witness_input_free(partial_witness);

    printf("\nSimulating double-signaling attack (same epoch, different message)\n");

    printf("\nHashing second signal\n");
    uint8_t signal2[32] = {11, 12, 13, 14, 15, 16, 17, 18, 19, 20};
    Vec_uint8_t signal2_vec = {signal2, 32, 32};
    CFr_t *x2 = ffi_hash_to_field_le(&signal2_vec);

    debug = ffi_cfr_debug(x2);
    printf("  - x2 = %s\n", debug.ptr);
    ffi_c_string_free(debug);

#ifndef MULTI_MESSAGE_ID
    printf("\nCreating second message with the same id\n");
    CFr_t *message_id2 = ffi_uint_to_cfr(0);

    debug = ffi_cfr_debug(message_id2);
    printf("  - message_id2 = %s\n", debug.ptr);
    ffi_c_string_free(debug);
#endif

#ifdef MULTI_MESSAGE_ID
    printf("\nCreating message_ids2 and selector_used2 (multi-message-id mode)\n");
    printf("  - using 2 out of %zu slots\n", max_out);
    printf("  - duplicated slot id 1\n");

    Vec_CFr_t message_ids2 = ffi_vec_cfr_new(max_out);
    {
        CFr_t *tmp;
        tmp = ffi_uint_to_cfr(1);
        ffi_vec_cfr_push(&message_ids2, tmp);
        ffi_cfr_free(tmp);
        tmp = ffi_cfr_zero();
        ffi_vec_cfr_push(&message_ids2, tmp);
        ffi_cfr_free(tmp);
        tmp = ffi_uint_to_cfr(3);
        ffi_vec_cfr_push(&message_ids2, tmp);
        ffi_cfr_free(tmp);
        for (size_t i = 3; i < max_out; i++)
        {
            tmp = ffi_cfr_zero();
            ffi_vec_cfr_push(&message_ids2, tmp);
            ffi_cfr_free(tmp);
        }
    }

    bool *selector_arr2 = calloc(max_out, sizeof(bool));
    selector_arr2[0] = true;
    selector_arr2[2] = true;
    Vec_bool_t selector_used2 = {
        .ptr = selector_arr2,
        .len = max_out,
        .cap = max_out};

    debug = ffi_vec_cfr_debug(&message_ids2);
    printf("  - message_ids2 = %s\n", debug.ptr);
    ffi_c_string_free(debug);
#endif

    printf("\nCreating second RLN Witness\n");
#ifdef STATELESS
    CResult_FFI_RLNWitnessInput_ptr_Vec_uint8_t witness_result2 =
#ifdef MULTI_MESSAGE_ID
        ffi_rln_witness_input_new(
            identity_secret,
            user_message_limit,
            &message_ids2,
            &path_elements,
            &identity_path_index,
            x2,
            external_nullifier,
            &selector_used2);
#else
        ffi_rln_witness_input_new(
            identity_secret,
            user_message_limit,
            message_id2,
            &path_elements,
            &identity_path_index,
            x2,
            external_nullifier);
#endif

    if (!witness_result2.ok)
    {
        fprintf(stderr, "Second RLN Witness creation error: %s\n", witness_result2.err.ptr);
        ffi_c_string_free(witness_result2.err);
        return EXIT_FAILURE;
    }
    FFI_RLNWitnessInput_t *witness2 = witness_result2.ok;
    printf("  - second RLN Witness created successfully\n");
#else
    CResult_FFI_RLNWitnessInput_ptr_Vec_uint8_t witness_result2 =
#ifdef MULTI_MESSAGE_ID
        ffi_rln_witness_input_new(
            identity_secret,
            user_message_limit,
            &message_ids2,
            &merkle_proof->path_elements,
            &merkle_proof->path_index,
            x2,
            external_nullifier,
            &selector_used2);
#else
        ffi_rln_witness_input_new(
            identity_secret,
            user_message_limit,
            message_id2,
            &merkle_proof->path_elements,
            &merkle_proof->path_index,
            x2,
            external_nullifier);
#endif

    if (!witness_result2.ok)
    {
        fprintf(stderr, "Second RLN Witness creation error: %s\n", witness_result2.err.ptr);
        ffi_c_string_free(witness_result2.err);
        return EXIT_FAILURE;
    }
    FFI_RLNWitnessInput_t *witness2 = witness_result2.ok;
    printf("  - second RLN Witness created successfully\n");
#endif

    printf("\nGenerating second RLN Proof\n");
    CResult_FFI_RLNProof_ptr_Vec_uint8_t proof_gen_result2 = ffi_generate_rln_proof(
        &rln,
        &witness2);

    if (!proof_gen_result2.ok)
    {
        fprintf(stderr, "Second proof generation error: %s\n", proof_gen_result2.err.ptr);
        ffi_c_string_free(proof_gen_result2.err);
        return EXIT_FAILURE;
    }

    FFI_RLNProof_t *rln_proof2 = proof_gen_result2.ok;
    printf("  - second proof generated successfully\n");

    FFI_RLNProofValues_t *proof_values2 = ffi_rln_proof_get_values(&rln_proof2);

    printf("\nVerifying second proof\n");
#ifdef STATELESS
    CBoolResult_t verify_err2 = ffi_verify_with_roots(&rln, &rln_proof2, &roots, x2);
#else
    CBoolResult_t verify_err2 = ffi_verify_rln_proof(&rln, &rln_proof2, x2);
#endif

    if (!verify_err2.ok)
    {
        fprintf(stderr, "Proof verification error: %s\n", verify_err2.err.ptr);
        ffi_c_string_free(verify_err2.err);
        return EXIT_FAILURE;
    }

    printf("  - second proof verified successfully\n");

    ffi_rln_proof_free(rln_proof2);

    printf("\nRecovering identity secret\n");
    CResult_CFr_ptr_Vec_uint8_t recover_result = ffi_recover_id_secret(&proof_values, &proof_values2);
    if (!recover_result.ok)
    {
        fprintf(stderr, "Identity recovery error: %s\n", recover_result.err.ptr);
        ffi_c_string_free(recover_result.err);
        return EXIT_FAILURE;
    }

    CFr_t *recovered_secret = recover_result.ok;

    debug = ffi_cfr_debug(recovered_secret);
    printf("  - recovered_secret = %s\n", debug.ptr);
    ffi_c_string_free(debug);

    debug = ffi_cfr_debug(identity_secret);
    printf("  - original_secret  = %s\n", debug.ptr);
    ffi_c_string_free(debug);

    printf("  - identity recovered successfully\n");

    ffi_cfr_free(recovered_secret);

    ffi_rln_proof_values_free(proof_values2);
    ffi_rln_proof_values_free(proof_values);
    ffi_cfr_free(x2);
#ifndef MULTI_MESSAGE_ID
    ffi_cfr_free(message_id2);
#endif

#ifdef STATELESS
    ffi_rln_witness_input_free(witness2);
    ffi_rln_witness_input_free(witness);
    ffi_vec_cfr_free(roots);
    ffi_vec_cfr_free(path_elements);
    for (size_t i = 0; i < TREE_DEPTH - 1; i++)
    {
        ffi_cfr_free(default_hashes[i]);
    }
    ffi_cfr_free(default_leaf);
    ffi_cfr_free(computed_root);
#else
    ffi_rln_witness_input_free(witness2);
    ffi_rln_witness_input_free(witness);
    ffi_merkle_proof_free(merkle_proof);
#endif

#ifdef MULTI_MESSAGE_ID
    ffi_vec_cfr_free(message_ids);
    ffi_vec_cfr_free(message_ids2);
    free(selector_arr);
    free(selector_arr2);
#endif

    ffi_cfr_free(rate_commitment);
    ffi_cfr_free(x);
    ffi_cfr_free(epoch);
    ffi_cfr_free(rln_identifier);
    ffi_cfr_free(external_nullifier);
    ffi_cfr_free(user_message_limit);
#ifndef MULTI_MESSAGE_ID
    ffi_cfr_free(message_id);
#endif
    ffi_vec_cfr_free(keys);
    ffi_rln_free(rln);

    return EXIT_SUCCESS;
}