#define _GNU_SOURCE
#include <stdio.h>
#include "debug.h"
#include <stdlib.h>
#include <string.h>
#include <time.h>

// Include nostr_core_lib for cJSON
#include "../nostr_core_lib/cjson/cJSON.h"

// Configuration management system
#include "config.h"

// NIP-40 Expiration configuration structure
struct expiration_config {
    int enabled;                    // 0 = disabled, 1 = enabled
    int strict_mode;                // 1 = reject expired events on submission
    int filter_responses;           // 1 = filter expired events from responses
    int delete_expired;             // 1 = delete expired events from DB (future feature)
    long grace_period;              // Grace period in seconds for clock skew
};

// Global expiration configuration instance
struct expiration_config g_expiration_config = {
    .enabled = 1,                   // Enable expiration handling by default
    .strict_mode = 1,               // Reject expired events on submission by default
    .filter_responses = 1,          // Filter expired events from responses by default
    .delete_expired = 0,            // Don't delete by default (keep for audit)
    .grace_period = 1               // 1 second grace period for testing (was 300)
};


// Initialize expiration configuration using configuration system
void init_expiration_config() {

    // Get all config values first (without holding mutex to avoid deadlock)
    int expiration_enabled = get_config_bool("expiration_enabled", 1);
    int expiration_strict = get_config_bool("expiration_strict", 1);
    int expiration_filter = get_config_bool("expiration_filter", 1);
    int expiration_delete = get_config_bool("expiration_delete", 0);
    long expiration_grace_period = get_config_int("expiration_grace_period", 1);

    // Load expiration settings from configuration system
    g_expiration_config.enabled = expiration_enabled;
    g_expiration_config.strict_mode = expiration_strict;
    g_expiration_config.filter_responses = expiration_filter;
    g_expiration_config.delete_expired = expiration_delete;
    g_expiration_config.grace_period = expiration_grace_period;

    // Validate grace period bounds
    if (g_expiration_config.grace_period < 0 || g_expiration_config.grace_period > 86400) {
        DEBUG_WARN("Invalid grace period, using default of 300 seconds");
        g_expiration_config.grace_period = 300;
    }

}

// Extract expiration timestamp from event tags
long extract_expiration_timestamp(cJSON* tags) {
    if (!tags || !cJSON_IsArray(tags)) {
        return 0; // No expiration
    }

    cJSON* tag = NULL;
    cJSON_ArrayForEach(tag, tags) {
        if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) {
            cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
            cJSON* tag_value = cJSON_GetArrayItem(tag, 1);

            if (cJSON_IsString(tag_name) && cJSON_IsString(tag_value)) {
                const char* name = cJSON_GetStringValue(tag_name);
                const char* value = cJSON_GetStringValue(tag_value);

                if (name && value && strcmp(name, "expiration") == 0) {
                    // Validate that the string contains only digits (and optional leading whitespace)
                    const char* p = value;

                    // Skip leading whitespace
                    while (*p == ' ' || *p == '\t') p++;

                    // Check if we have at least one digit
                    if (*p == '\0') {
                        continue; // Empty or whitespace-only string, ignore this tag
                    }

                    // Validate that all remaining characters are digits
                    const char* digit_start = p;
                    while (*p >= '0' && *p <= '9') p++;

                    // If we didn't consume the entire string or found no digits, it's malformed
                    if (*p != '\0' || p == digit_start) {
                        char debug_msg[256];
                        snprintf(debug_msg, sizeof(debug_msg),
                                "Ignoring malformed expiration tag value: '%.32s'", value);
                        DEBUG_WARN(debug_msg);
                        continue; // Ignore malformed expiration tag
                    }

                    long expiration_ts = atol(value);
                    if (expiration_ts > 0) {
                        return expiration_ts;
                    }
                }
            }
        }
    }

    return 0; // No valid expiration tag found
}

// Check if event is currently expired
int is_event_expired(cJSON* event, time_t current_time) {
    if (!event) {
        return 0; // Invalid event, not expired
    }

    cJSON* tags = cJSON_GetObjectItem(event, "tags");
    long expiration_ts = extract_expiration_timestamp(tags);

    if (expiration_ts == 0) {
        return 0; // No expiration timestamp, not expired
    }

    // Check if current time exceeds expiration + grace period
    return (current_time > (expiration_ts + g_expiration_config.grace_period));
}

// Validate event expiration according to NIP-40
int validate_event_expiration(cJSON* event, char* error_message, size_t error_size) {
    if (!g_expiration_config.enabled) {
        return 0; // Expiration validation disabled
    }

    if (!event) {
        snprintf(error_message, error_size, "expiration: null event");
        return -1;
    }

    // Check if event is expired
    time_t current_time = time(NULL);
    if (is_event_expired(event, current_time)) {
        if (g_expiration_config.strict_mode) {
            cJSON* tags = cJSON_GetObjectItem(event, "tags");
            long expiration_ts = extract_expiration_timestamp(tags);

            snprintf(error_message, error_size,
                    "invalid: event expired (expiration=%ld, current=%ld, grace=%ld)",
                    expiration_ts, (long)current_time, g_expiration_config.grace_period);
            DEBUG_WARN("Event rejected: expired timestamp");
            return -1;
        } else {
            // In non-strict mode, allow expired events
        }
    }

    return 0; // Success
}