#define _POSIX_C_SOURCE 200809L

#include "signer_health.h"

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

#include "../nostr_core_lib/nostr_core/nostr_common.h"

static pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER;
static signer_health_state_t g_state = SIGNER_HEALTH_UNKNOWN;
static char g_mode[32] = {0};
static char g_last_reason[SIGNER_HEALTH_REASON_LEN] = {0};
static time_t g_last_error_ts = 0;
static time_t g_last_ok_ts = 0;

void signer_health_set_mode(const char* mode) {
    pthread_mutex_lock(&g_lock);
    if (mode && mode[0]) {
        snprintf(g_mode, sizeof(g_mode), "%s", mode);
    } else {
        g_mode[0] = '\0';
    }
    pthread_mutex_unlock(&g_lock);
}

void signer_health_reset(void) {
    pthread_mutex_lock(&g_lock);
    g_state = SIGNER_HEALTH_UNKNOWN;
    g_mode[0] = '\0';
    g_last_reason[0] = '\0';
    g_last_error_ts = 0;
    g_last_ok_ts = 0;
    pthread_mutex_unlock(&g_lock);
}

/* Classify a nostr error code into a short category label used both for
 * the health reason string and for log clarity. The categories map to
 * the failure modes called out in Phase 4 item 18:
 *   transport/IO  - network or socket failure talking to n_signer
 *   auth/policy   - n_signer rejected the request (auth envelope, policy)
 *   crypto        - the signer performed work but the crypto op failed
 *                   (e.g. decrypt of malformed ciphertext). This is NOT
 *                   necessarily a signer outage, but we still record it
 *                   so the operator can see recent signer activity.
 *   unknown       - anything else.
 *
 * Note: nostr_core_lib does not currently expose dedicated nsigner
 * transport/auth error codes; remote signer failures surface as
 * NOSTR_ERROR_IO_FAILED / NOSTR_ERROR_NETWORK_FAILED (transport) or
 * NOSTR_ERROR_CRYPTO_FAILED (the nsigner client maps server-side
 * rejections to crypto-failed when it cannot be more specific). We
 * treat IO/network as the strong "signer unreachable" signal. */
static const char* classify_rc(int rc) {
    if (rc == NOSTR_ERROR_IO_FAILED || rc == NOSTR_ERROR_NETWORK_FAILED) {
        return "transport/IO";
    }
    if (rc == NOSTR_ERROR_CRYPTO_FAILED) {
        return "crypto/auth";
    }
    if (rc == NOSTR_ERROR_MEMORY_FAILED) {
        return "resource";
    }
    if (rc == NOSTR_ERROR_INVALID_INPUT) {
        return "invalid-input";
    }
    return "unknown";
}

void signer_health_record_ok(void) {
    int transitioned_from_unhealthy = 0;
    pthread_mutex_lock(&g_lock);
    time_t now = time(NULL);
    if (g_state == SIGNER_HEALTH_UNHEALTHY) {
        transitioned_from_unhealthy = 1;
    }
    g_state = SIGNER_HEALTH_HEALTHY;
    g_last_ok_ts = now;
    /* Keep last_reason/last_error_ts so the operator can see the most
     * recent prior failure even after recovery. */
    pthread_mutex_unlock(&g_lock);

    if (transitioned_from_unhealthy) {
        fprintf(stderr,
                "[didactyl] signer health transition: unhealthy -> healthy (mode=%s)\n",
                g_mode[0] ? g_mode : "local");
    }
}

void signer_health_record_failure(int nostr_rc, const char* detail) {
    int transitioned_from_healthy = 0;
    char reason[SIGNER_HEALTH_REASON_LEN];
    const char* cls = classify_rc(nostr_rc);
    if (detail && detail[0]) {
        snprintf(reason, sizeof(reason), "%s: %s (rc=%d)", cls, detail, nostr_rc);
    } else {
        snprintf(reason, sizeof(reason), "%s (rc=%d)", cls, nostr_rc);
    }

    pthread_mutex_lock(&g_lock);
    time_t now = time(NULL);
    if (g_state == SIGNER_HEALTH_HEALTHY) {
        transitioned_from_healthy = 1;
    }
    g_state = SIGNER_HEALTH_UNHEALTHY;
    snprintf(g_last_reason, sizeof(g_last_reason), "%s", reason);
    g_last_error_ts = now;
    pthread_mutex_unlock(&g_lock);

    if (transitioned_from_healthy) {
        fprintf(stderr,
                "[didactyl] signer health transition: healthy -> unhealthy "
                "(mode=%s, reason=%s)\n",
                g_mode[0] ? g_mode : "local",
                reason);
    }
}

int signer_health_snapshot(signer_health_snapshot_t* out) {
    if (!out) {
        return -1;
    }
    pthread_mutex_lock(&g_lock);
    out->state = g_state;
    snprintf(out->mode, sizeof(out->mode), "%s", g_mode);
    snprintf(out->last_reason, sizeof(out->last_reason), "%s", g_last_reason);
    out->last_error_ts = g_last_error_ts;
    out->last_ok_ts = g_last_ok_ts;
    pthread_mutex_unlock(&g_lock);
    return 0;
}
