#define _POSIX_C_SOURCE 200809L

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

#include "./nostr_core_lib/cjson/cJSON.h"
#include "./nostr_core_lib/nostr_core/nostr_core.h"

static const char* k_default_relays[] = {
    "wss://relay.damus.io",
    "wss://relay.primal.net",
    "wss://relay.laantungir.net"
};

typedef struct {
    int eose_received;
    int events_received;
    int kind3_events;
    int max_p_tags;
} test_ctx_t;

static volatile int g_ws_eose_seen = 0;

static long long now_ms(void) {
    struct timespec ts;
    if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
        return 0;
    }
    return ((long long)ts.tv_sec * 1000LL) + (ts.tv_nsec / 1000000LL);
}

static int count_tag(cJSON* tags, const char* key) {
    if (!tags || !cJSON_IsArray(tags) || !key) {
        return 0;
    }

    int n = cJSON_GetArraySize(tags);
    int count = 0;
    for (int i = 0; i < n; i++) {
        cJSON* tag = cJSON_GetArrayItem(tags, i);
        if (!tag || !cJSON_IsArray(tag) || cJSON_GetArraySize(tag) < 1) {
            continue;
        }

        cJSON* k = cJSON_GetArrayItem(tag, 0);
        if (k && cJSON_IsString(k) && k->valuestring && strcmp(k->valuestring, key) == 0) {
            count++;
        }
    }
    return count;
}

static void on_event(cJSON* event, const char* relay_url, void* user_data) {
    test_ctx_t* ctx = (test_ctx_t*)user_data;
    if (!ctx || !event || !cJSON_IsObject(event)) {
        return;
    }

    ctx->events_received++;

    cJSON* kind = cJSON_GetObjectItemCaseSensitive(event, "kind");
    cJSON* id = cJSON_GetObjectItemCaseSensitive(event, "id");
    cJSON* pubkey = cJSON_GetObjectItemCaseSensitive(event, "pubkey");
    cJSON* created_at = cJSON_GetObjectItemCaseSensitive(event, "created_at");
    cJSON* content = cJSON_GetObjectItemCaseSensitive(event, "content");
    cJSON* tags = cJSON_GetObjectItemCaseSensitive(event, "tags");

    int kind_val = (kind && cJSON_IsNumber(kind)) ? (int)kind->valuedouble : -1;
    int p_tags = count_tag(tags, "p");
    int r_tags = count_tag(tags, "r");
    int tag_count = (tags && cJSON_IsArray(tags)) ? cJSON_GetArraySize(tags) : 0;
    int content_len = (content && cJSON_IsString(content) && content->valuestring) ? (int)strlen(content->valuestring) : 0;

    if (p_tags > ctx->max_p_tags) {
        ctx->max_p_tags = p_tags;
    }
    if (kind_val == 3) {
        ctx->kind3_events++;
    }

    printf("[EVENT] relay=%s kind=%d created_at=%lld tags=%d p_tags=%d r_tags=%d content_len=%d id=%.16s pubkey=%.16s\n",
           relay_url ? relay_url : "<unknown>",
           kind_val,
           (created_at && cJSON_IsNumber(created_at)) ? (long long)created_at->valuedouble : -1LL,
           tag_count,
           p_tags,
           r_tags,
           content_len,
           (id && cJSON_IsString(id) && id->valuestring) ? id->valuestring : "",
           (pubkey && cJSON_IsString(pubkey) && pubkey->valuestring) ? pubkey->valuestring : "");
}

static void on_eose(cJSON** events, int event_count, void* user_data) {
    (void)events;
    test_ctx_t* ctx = (test_ctx_t*)user_data;
    if (!ctx) {
        return;
    }

    ctx->eose_received = 1;
    printf("[EOSE] received for subscription (event_count=%d)\n", event_count);
}

static void log_callback(int level, const char* component, const char* message, void* user_data) {
    (void)user_data;
    const char* lvl = "?";
    switch (level) {
        case NOSTR_LOG_LEVEL_ERROR: lvl = "ERROR"; break;
        case NOSTR_LOG_LEVEL_WARN: lvl = "WARN"; break;
        case NOSTR_LOG_LEVEL_INFO: lvl = "INFO"; break;
        case NOSTR_LOG_LEVEL_DEBUG: lvl = "DEBUG"; break;
        case NOSTR_LOG_LEVEL_TRACE: lvl = "TRACE"; break;
        default: break;
    }

    if (!component) component = "core";
    if (!message) message = "";

    if (strcmp(component, "websocket") == 0) {
        printf("[NOSTR %s] [%s] %s\n", lvl, component, message);
        if (strstr(message, "[\"EOSE\"") != NULL) {
            g_ws_eose_seen = 1;
        }
    }
}

int main(int argc, char** argv) {
    const char* npub = "npub13lm5wf8dvsdnc2894pkhch9uf8phvw9varrv8zf4sc885hhdmc8q6lx7ks";
    int timeout_ms = 15000;

    if (argc >= 2 && argv[1] && argv[1][0] != '\0') {
        npub = argv[1];
    }
    if (argc >= 3) {
        timeout_ms = atoi(argv[2]);
        if (timeout_ms <= 0) {
            timeout_ms = 15000;
        }
    }

    unsigned char pubkey[32] = {0};
    char pubkey_hex[65] = {0};

    if (nostr_decode_npub(npub, pubkey) != 0) {
        fprintf(stderr, "Failed to decode npub: %s\n", npub);
        return 1;
    }
    nostr_bytes_to_hex(pubkey, 32, pubkey_hex);

    if (nostr_crypto_init() != 0) {
        fprintf(stderr, "nostr_crypto_init failed\n");
        return 1;
    }

    nostr_set_log_callback(log_callback, NULL);
    nostr_set_log_level(NOSTR_LOG_LEVEL_TRACE);

    nostr_pool_reconnect_config_t* reconnect = nostr_pool_reconnect_config_default();
    if (!reconnect) {
        fprintf(stderr, "nostr_pool_reconnect_config_default failed\n");
        nostr_crypto_cleanup();
        return 1;
    }

    reconnect->enable_auto_reconnect = 1;
    reconnect->ping_interval_seconds = 20;
    reconnect->pong_timeout_seconds = 10;

    nostr_relay_pool_t* pool = nostr_relay_pool_create(reconnect);
    if (!pool) {
        fprintf(stderr, "nostr_relay_pool_create failed\n");
        nostr_crypto_cleanup();
        return 1;
    }

    const int relay_count = (int)(sizeof(k_default_relays) / sizeof(k_default_relays[0]));
    for (int i = 0; i < relay_count; i++) {
        if (nostr_relay_pool_add_relay(pool, k_default_relays[i]) != 0) {
            fprintf(stderr, "Failed to add relay: %s\n", k_default_relays[i]);
        }
    }

    cJSON* filter = cJSON_CreateObject();
    cJSON* kinds = cJSON_CreateArray();
    cJSON* authors = cJSON_CreateArray();
    if (!filter || !kinds || !authors) {
        fprintf(stderr, "Failed to build filter JSON\n");
        cJSON_Delete(filter);
        cJSON_Delete(kinds);
        cJSON_Delete(authors);
        nostr_relay_pool_destroy(pool);
        nostr_crypto_cleanup();
        return 1;
    }

    cJSON_AddItemToArray(kinds, cJSON_CreateNumber(3));
    cJSON_AddItemToObject(filter, "kinds", kinds);
    cJSON_AddItemToArray(authors, cJSON_CreateString(pubkey_hex));
    cJSON_AddItemToObject(filter, "authors", authors);
    cJSON_AddNumberToObject(filter, "limit", 16);

    test_ctx_t ctx;
    memset(&ctx, 0, sizeof(ctx));

    printf("Target npub: %s\n", npub);
    printf("Target hex : %s\n", pubkey_hex);
    printf("Relays (%d):\n", relay_count);
    for (int i = 0; i < relay_count; i++) {
        printf("  - %s\n", k_default_relays[i]);
    }
    printf("Subscribing to kind=3 with timeout=%d ms\n", timeout_ms);

    nostr_pool_subscription_t* sub = nostr_relay_pool_subscribe(
        pool,
        k_default_relays,
        relay_count,
        filter,
        on_event,
        on_eose,
        &ctx,
        0,
        0,
        NOSTR_POOL_EOSE_FIRST,
        12,
        20
    );

    if (!sub) {
        fprintf(stderr, "nostr_relay_pool_subscribe failed\n");
        cJSON_Delete(filter);
        nostr_relay_pool_destroy(pool);
        nostr_crypto_cleanup();
        return 1;
    }

    long long started = now_ms();
    while (!ctx.eose_received && !g_ws_eose_seen) {
        long long elapsed = now_ms() - started;
        if (elapsed >= timeout_ms) {
            break;
        }
        (void)nostr_relay_pool_poll(pool, 100);
    }

    printf("\nSummary:\n");
    printf("  EOSE callback : %s\n", ctx.eose_received ? "yes" : "no");
    printf("  Websocket EOSE: %s\n", g_ws_eose_seen ? "yes" : "no");
    printf("  Events seen   : %d\n", ctx.events_received);
    printf("  Kind-3 events : %d\n", ctx.kind3_events);
    printf("  Max p-tags    : %d\n", ctx.max_p_tags);

    if (!ctx.eose_received && g_ws_eose_seen) {
        fprintf(stderr, "NOTE: Relay sent EOSE on websocket but subscription callback did not fire (pool EOSE behavior mismatch).\n");
    }
    if (!ctx.eose_received && !g_ws_eose_seen) {
        fprintf(stderr, "Timed out waiting for EOSE after %d ms\n", timeout_ms);
    }

    (void)nostr_pool_subscription_close(sub);
    cJSON_Delete(filter);
    nostr_relay_pool_destroy(pool);
    nostr_crypto_cleanup();

    return (ctx.eose_received || g_ws_eose_seen) ? 0 : 2;
}
