/* Test for mine_event verb (NIP-13 Proof-of-Work) */
/* NSIGNER_HEADERLESS_DECLS_BEGIN */
#include <stddef.h>
#include <stdint.h>
#include <sys/types.h>
#include <cJSON.h>

/* from secure_mem.h */

typedef struct {
    void *data;
    size_t size;
    int locked;
} secure_buf_t;

int secure_buf_alloc(secure_buf_t *buf, size_t size);
void secure_buf_free(secure_buf_t *buf);
void secure_memzero(void *ptr, size_t len);

/* from mnemonic.h */

#define MNEMONIC_MAX_LEN 256

typedef struct {
    secure_buf_t buf;
    int loaded;
    int word_count;
} mnemonic_state_t;

void mnemonic_init(mnemonic_state_t *state);
int mnemonic_load(mnemonic_state_t *state, const char *phrase);
void mnemonic_unload(mnemonic_state_t *state);
int mnemonic_is_loaded(const mnemonic_state_t *state);
const char *mnemonic_get_phrase(const mnemonic_state_t *state);
int mnemonic_generate(int word_count, char *out, size_t out_len);

/* from role_table.h */

#define ROLE_NAME_MAX 64
#define ROLE_PATH_MAX 128
#define ROLE_PURPOSE_MAX 32
#define ROLE_CURVE_MAX 16
#define ROLE_PUBKEY_HEX_MAX 66
#define ROLE_TABLE_MAX_ENTRIES 256

typedef enum {
    PURPOSE_NOSTR = 0,
    PURPOSE_BITCOIN,
    PURPOSE_SSH,
    PURPOSE_AGE,
    PURPOSE_FIPS,
    PURPOSE_UNKNOWN
} role_purpose_t;

typedef enum {
    CURVE_SECP256K1 = 0,
    CURVE_ED25519,
    CURVE_X25519,
    CURVE_UNKNOWN
} role_curve_t;

typedef enum {
    SELECTOR_NOSTR_INDEX,
    SELECTOR_ROLE_PATH
} role_selector_type_t;

typedef struct {
    char name[ROLE_NAME_MAX];
    char purpose_str[ROLE_PURPOSE_MAX];
    char curve_str[ROLE_CURVE_MAX];
    role_purpose_t purpose;
    role_curve_t curve;
    role_selector_type_t selector_type;
    int nostr_index;
    char role_path[ROLE_PATH_MAX];
    char pubkey_hex[ROLE_PUBKEY_HEX_MAX];
    int derived;
} role_entry_t;

typedef struct {
    role_entry_t entries[ROLE_TABLE_MAX_ENTRIES];
    int count;
} role_table_t;

void role_table_init(role_table_t *table);
int role_table_add(role_table_t *table, const role_entry_t *entry);
role_entry_t *role_table_find_by_name(role_table_t *table, const char *name);
role_entry_t *role_table_find_by_nostr_index(role_table_t *table, int index);
role_entry_t *role_table_find_by_path(role_table_t *table, const char *path);
role_entry_t *role_table_get_default(role_table_t *table);
role_purpose_t role_purpose_from_str(const char *s);
role_curve_t role_curve_from_str(const char *s);
const char *role_purpose_to_str(role_purpose_t p);
const char *role_curve_to_str(role_curve_t c);
int role_table_register_nostr_index(role_table_t *table, int nostr_index);

/* from selector.h */

#define SELECTOR_OK                 0
#define SELECTOR_ERR_AMBIGUOUS     -1
#define SELECTOR_ERR_NOT_FOUND     -2
#define SELECTOR_ERR_NO_DEFAULT    -3

typedef struct {
    int has_role;
    char role_name[ROLE_NAME_MAX];
    int has_nostr_index;
    int nostr_index;
    int has_role_path;
    char role_path[ROLE_PATH_MAX];
} selector_request_t;

void selector_request_init(selector_request_t *req);
int selector_resolve(const selector_request_t *req, role_table_t *table, role_entry_t **out);
const char *selector_strerror(int err);

/* from enforcement.h */

#define ENFORCE_OK                0
#define ENFORCE_ERR_PURPOSE      -1
#define ENFORCE_ERR_CURVE        -2
#define ENFORCE_ERR_UNKNOWN_VERB -3

#define VERB_SIGN_EVENT     "sign_event"
#define VERB_GET_PUBLIC_KEY "get_public_key"
#define VERB_NIP44_ENCRYPT  "nip44_encrypt"
#define VERB_NIP44_DECRYPT  "nip44_decrypt"
#define VERB_NIP04_ENCRYPT  "nip04_encrypt"
#define VERB_NIP04_DECRYPT  "nip04_decrypt"
#define VERB_MINE_EVENT     "mine_event"

int enforce_verb_role(const char *verb, const role_entry_t *role);
const char *enforce_strerror(int err);

/* from policy.h */

#define POLICY_MAX_ENTRIES    32
#define POLICY_MAX_VERBS      16
#define POLICY_MAX_ROLES      16
#define POLICY_MAX_PURPOSES   8
#define POLICY_VERB_MAX_LEN   32
#define POLICY_CALLER_MAX_LEN 160

typedef enum {
    PROMPT_NEVER = 0,
    PROMPT_FIRST_PER_BOOT,
    PROMPT_EVERY_REQUEST,
    PROMPT_DENY
} prompt_mode_t;

typedef enum {
    POLICY_SOURCE_DEFAULT = 0,
    POLICY_SOURCE_PREAPPROVE,
    POLICY_SOURCE_SESSION_GRANT
} policy_source_t;

typedef struct {
    char caller[POLICY_CALLER_MAX_LEN];
    char verbs[POLICY_MAX_VERBS][POLICY_VERB_MAX_LEN];
    int verb_count;
    char roles[POLICY_MAX_ROLES][ROLE_NAME_MAX];
    int role_count;
    char purposes[POLICY_MAX_PURPOSES][ROLE_PURPOSE_MAX];
    int purpose_count;
    prompt_mode_t prompt;
    policy_source_t source;
} policy_entry_t;

typedef struct {
    policy_entry_t entries[POLICY_MAX_ENTRIES];
    int count;
} policy_table_t;

#define POLICY_ALLOW     0
#define POLICY_DENY     -1
#define POLICY_PROMPT   -2
#define POLICY_NO_MATCH -3

void policy_table_init(policy_table_t *table);
void policy_init_default(policy_table_t *table, uid_t owner_uid);
int policy_table_add(policy_table_t *table, const policy_entry_t *entry);
int policy_check(const policy_table_t *table, const char *caller_id,
                 const char *verb, const char *role_name, const char *purpose,
                 policy_source_t *out_source);
prompt_mode_t prompt_mode_from_str(const char *s);
const char *prompt_mode_to_str(prompt_mode_t m);

/* from crypto.h */

typedef struct {
    secure_buf_t private_key;
    unsigned char public_key[32];
    char pubkey_hex[65];
    char npub[128];
    int valid;
} derived_key_t;

typedef struct {
    derived_key_t keys[ROLE_TABLE_MAX_ENTRIES];
    int count;
} key_store_t;

int crypto_derive_all(key_store_t *store, role_table_t *table, const mnemonic_state_t *mnemonic);
int crypto_derive_one(key_store_t *store, role_table_t *table, const mnemonic_state_t *mnemonic, int role_index);
const unsigned char *crypto_get_private_key(const key_store_t *store, int role_index);
const char *crypto_get_pubkey_hex(const key_store_t *store, int role_index);
char *crypto_sign_event(const key_store_t *store, int role_index, const char *event_json);
char *crypto_mine_event(const key_store_t *store, int role_index,
                        const char *event_json, int difficulty,
                        int threads, int timeout_sec);
char *crypto_nip44_encrypt(const key_store_t *store, int role_index,
                           const char *recipient_pubkey_hex, const char *plaintext);
char *crypto_nip44_decrypt(const key_store_t *store, int role_index,
                           const char *sender_pubkey_hex, const char *ciphertext);
char *crypto_nip04_encrypt(const key_store_t *store, int role_index,
                           const char *recipient_pubkey_hex, const char *plaintext);
char *crypto_nip04_decrypt(const key_store_t *store, int role_index,
                           const char *sender_pubkey_hex, const char *ciphertext);
void crypto_wipe(key_store_t *store);

/* from dispatcher.h */

typedef struct {
    role_table_t *role_table;
    mnemonic_state_t *mnemonic;
    key_store_t *key_store;
} dispatcher_ctx_t;

void dispatcher_init(dispatcher_ctx_t *ctx, role_table_t *table, mnemonic_state_t *mnemonic, key_store_t *key_store);
char *dispatcher_handle_request(dispatcher_ctx_t *ctx, const char *json_request);

/* NSIGNER_HEADERLESS_DECLS_END */

#include <nostr_core/nostr_common.h>
#include <nostr_core/nip013.h>

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

static int g_passes = 0;
static int g_total = 0;

static void check_condition(const char *name, int condition) {
    g_total++;
    if (condition) {
        printf("PASS: %s\n", name);
        g_passes++;
    } else {
        printf("FAIL: %s\n", name);
    }
}

static int response_has(const char *response, const char *needle) {
    return (response != NULL && needle != NULL && strstr(response, needle) != NULL);
}

/* Parse a dispatcher response and extract the result object (for mine_event).
 * Returns a newly-allocated cJSON result object, or NULL on failure.
 * Caller must delete the returned object. */
static cJSON *extract_result_object(const char *response) {
    cJSON *resp_json = NULL;
    cJSON *result_str = NULL;
    cJSON *result_obj = NULL;

    if (response == NULL) return NULL;

    resp_json = cJSON_Parse(response);
    if (!resp_json) return NULL;

    result_str = cJSON_GetObjectItemCaseSensitive(resp_json, "result");
    if (!result_str || !cJSON_IsString(result_str)) {
        cJSON_Delete(resp_json);
        return NULL;
    }

    result_obj = cJSON_Parse(result_str->valuestring);
    cJSON_Delete(resp_json);
    return result_obj;
}

/* Check if target_reached in the result object matches expected */
static int check_target_reached(const char *response, int expected) {
    cJSON *result_obj = extract_result_object(response);
    cJSON *tr;
    int val = -1;

    if (!result_obj) return 0;

    tr = cJSON_GetObjectItemCaseSensitive(result_obj, "target_reached");
    if (tr && cJSON_IsBool(tr)) {
        val = cJSON_IsTrue(tr) ? 1 : 0;
    }

    cJSON_Delete(result_obj);
    return (val == expected);
}

static role_entry_t make_nostr_entry(const char *name, int idx) {
    role_entry_t e;
    memset(&e, 0, sizeof(e));
    strncpy(e.name, name, sizeof(e.name) - 1);
    strncpy(e.purpose_str, "nostr", sizeof(e.purpose_str) - 1);
    strncpy(e.curve_str, "secp256k1", sizeof(e.curve_str) - 1);
    e.purpose = role_purpose_from_str(e.purpose_str);
    e.curve = role_curve_from_str(e.curve_str);
    e.selector_type = SELECTOR_NOSTR_INDEX;
    e.nostr_index = idx;
    e.derived = 0;
    return e;
}

int main(void) {
    role_table_t table;
    role_entry_t main_role;
    mnemonic_state_t mnemonic;
    dispatcher_ctx_t dispatcher;
    key_store_t key_store;
    const char *valid_12 = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
    char *resp;
    int derived;

    role_table_init(&table);
    main_role = make_nostr_entry("main", 0);
    role_table_add(&table, &main_role);

    mnemonic_init(&mnemonic);
    mnemonic_load(&mnemonic, valid_12);

    memset(&key_store, 0, sizeof(key_store));
    dispatcher_init(&dispatcher, &table, &mnemonic, &key_store);

    derived = nostr_init();
    check_condition("nostr_init succeeds", derived == 0);

    derived = crypto_derive_all(&key_store, &table, &mnemonic);
    check_condition("crypto_derive_all derives at least one key", derived >= 1);

    /* 1. mine_event with low difficulty (2) and short timeout (5 sec) — should reach target */
    resp = dispatcher_handle_request(&dispatcher,
        "{\"id\":\"1\",\"method\":\"mine_event\",\"params\":["
        "\"{\\\"kind\\\":1,\\\"content\\\":\\\"hello pow\\\",\\\"tags\\\":[]}\","
        "{\"difficulty\":2,\"threads\":2,\"timeout_sec\":5}]}");
    check_condition("mine_event low difficulty returns result object",
        response_has(resp, "\"id\":\"1\"") && response_has(resp, "\"result\"") && response_has(resp, "achieved_difficulty"));
    check_condition("mine_event low difficulty returns signed event with nonce tag",
        response_has(resp, "nonce") && response_has(resp, "sig") && response_has(resp, "pubkey"));
    check_condition("mine_event low difficulty target_reached is true",
        check_target_reached(resp, 1));
    free(resp);

    /* 2. mine_event with high difficulty (30) and short timeout (2 sec) — should NOT reach target */
    resp = dispatcher_handle_request(&dispatcher,
        "{\"id\":\"2\",\"method\":\"mine_event\",\"params\":["
        "\"{\\\"kind\\\":1,\\\"content\\\":\\\"hard pow\\\",\\\"tags\\\":[]}\","
        "{\"difficulty\":30,\"threads\":4,\"timeout_sec\":2}]}");
    check_condition("mine_event high difficulty returns result object",
        response_has(resp, "\"id\":\"2\"") && response_has(resp, "\"result\"") && response_has(resp, "achieved_difficulty"));
    check_condition("mine_event high difficulty target_reached is false",
        check_target_reached(resp, 0));
    check_condition("mine_event high difficulty still returns a signed event",
        response_has(resp, "nonce") && response_has(resp, "sig"));
    free(resp);

    /* 3. mine_event with only timeout (no difficulty) — should mine for full duration */
    resp = dispatcher_handle_request(&dispatcher,
        "{\"id\":\"3\",\"method\":\"mine_event\",\"params\":["
        "\"{\\\"kind\\\":1,\\\"content\\\":\\\"timeout only\\\",\\\"tags\\\":[]}\","
        "{\"threads\":2,\"timeout_sec\":2}]}");
    check_condition("mine_event timeout-only returns result",
        response_has(resp, "\"id\":\"3\"") && response_has(resp, "\"result\"") && response_has(resp, "achieved_difficulty"));
    check_condition("mine_event timeout-only target_reached is false (no target set)",
        check_target_reached(resp, 0));
    free(resp);

    /* 4. mine_event with neither difficulty nor timeout — should error */
    resp = dispatcher_handle_request(&dispatcher,
        "{\"id\":\"4\",\"method\":\"mine_event\",\"params\":["
        "\"{\\\"kind\\\":1,\\\"content\\\":\\\"no params\\\",\\\"tags\\\":[]}\","
        "{\"threads\":2}]}");
    check_condition("mine_event with no termination condition returns 1007",
        response_has(resp, "\"id\":\"4\"") && response_has(resp, "\"code\":1007") && response_has(resp, "no_termination_condition"));
    free(resp);

    /* 5. mine_event with only difficulty (no timeout) — should use safety timeout and reach low target */
    resp = dispatcher_handle_request(&dispatcher,
        "{\"id\":\"5\",\"method\":\"mine_event\",\"params\":["
        "\"{\\\"kind\\\":1,\\\"content\\\":\\\"difficulty only\\\",\\\"tags\\\":[]}\","
        "{\"difficulty\":1,\"threads\":2}]}");
    check_condition("mine_event difficulty-only returns result and reaches target",
        response_has(resp, "\"id\":\"5\"") && response_has(resp, "\"result\"") && check_target_reached(resp, 1));
    free(resp);

    /* 6. mine_event with invalid event JSON */
    resp = dispatcher_handle_request(&dispatcher,
        "{\"id\":\"6\",\"method\":\"mine_event\",\"params\":["
        "\"not valid json\","
        "{\"difficulty\":1,\"timeout_sec\":2}]}");
    check_condition("mine_event with invalid event returns error",
        response_has(resp, "\"id\":\"6\"") && (response_has(resp, "\"code\":1008") || response_has(resp, "\"code\":-32602")));
    free(resp);

    /* 7. Verify the mined event's difficulty via nip013 validation */
    {
        cJSON *resp_json, *result_str, *result_obj, *event_str, *event_json, *id_item;
        const char *id_hex;
        int pow_diff;

        resp = dispatcher_handle_request(&dispatcher,
            "{\"id\":\"7\",\"method\":\"mine_event\",\"params\":["
            "\"{\\\"kind\\\":1,\\\"content\\\":\\\"verify pow\\\",\\\"tags\\\":[]}\","
            "{\"difficulty\":4,\"threads\":4,\"timeout_sec\":5}]}");

        resp_json = cJSON_Parse(resp);
        if (resp_json) {
            result_str = cJSON_GetObjectItemCaseSensitive(resp_json, "result");
            if (result_str && cJSON_IsString(result_str)) {
                result_obj = cJSON_Parse(result_str->valuestring);
                if (result_obj) {
                    event_str = cJSON_GetObjectItemCaseSensitive(result_obj, "event");
                    if (event_str && cJSON_IsString(event_str)) {
                        event_json = cJSON_Parse(event_str->valuestring);
                        if (event_json) {
                            id_item = cJSON_GetObjectItemCaseSensitive(event_json, "id");
                            if (id_item && cJSON_IsString(id_item)) {
                                id_hex = cJSON_GetStringValue(id_item);
                                pow_diff = nostr_calculate_pow_difficulty(id_hex);
                                check_condition("mined event has difficulty >= 4",
                                    pow_diff >= 4);
                            } else {
                                check_condition("mined event has difficulty >= 4", 0);
                            }
                            cJSON_Delete(event_json);
                        } else {
                            check_condition("mined event has difficulty >= 4", 0);
                        }
                    } else {
                        check_condition("mined event has difficulty >= 4", 0);
                    }
                    cJSON_Delete(result_obj);
                } else {
                    check_condition("mined event has difficulty >= 4", 0);
                }
            } else {
                check_condition("mined event has difficulty >= 4", 0);
            }
            cJSON_Delete(resp_json);
        } else {
            check_condition("mined event has difficulty >= 4", 0);
        }
        free(resp);
    }

    crypto_wipe(&key_store);
    nostr_cleanup();

    printf("%d/%d tests passed\n", g_passes, g_total);
    return (g_passes == g_total) ? 0 : 1;
}
