/*
 * BUD-09 - Blob Report (NIP-56 Report Events)
 * Handles reporting of inappropriate or harmful blob content
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sqlite3.h>
#include <fcgi_stdio.h>
#include <time.h>
#include "ginxsom.h"

// Use global database path from main.c
extern char g_db_path[];

// Forward declarations for helper functions
void send_error_response(int status_code, const char* error_type, const char* message, const char* details);
void log_request(const char* method, const char* uri, const char* auth_status, int status_code);
int validate_sha256_format(const char* sha256);

/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
//             BUD 09 - Blob Report (NIP-56 Report Events)
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

// Validate NIP-56 report event structure
int validate_report_event_structure(cJSON* event) {
    if (!event) {
        return 0;
    }
    
    // Must be kind 1984
    cJSON* kind_json = cJSON_GetObjectItem(event, "kind");
    if (!kind_json || !cJSON_IsNumber(kind_json)) {
        return 0;
    }
    if (cJSON_GetNumberValue(kind_json) != 1984) {
        return 0;
    }
    
    // Must have tags array
    cJSON* tags = cJSON_GetObjectItem(event, "tags");
    if (!tags || !cJSON_IsArray(tags)) {
        return 0;
    }
    
    // Must have at least one 'x' tag
    int has_x_tag = 0;
    cJSON* tag = NULL;
    cJSON_ArrayForEach(tag, tags) {
        if (!cJSON_IsArray(tag)) continue;
        cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
        if (tag_name && cJSON_IsString(tag_name)) {
            const char* tag_name_str = cJSON_GetStringValue(tag_name);
            if (tag_name_str && strcmp(tag_name_str, "x") == 0) {
                has_x_tag = 1;
                break;
            }
        }
    }
    
    return has_x_tag;
}

// Extract SHA-256 blob hashes from 'x' tags
int extract_blob_hashes_from_report(cJSON* event, char blob_hashes[][65], int max_hashes) {
    if (!event || !blob_hashes) {
        return 0;
    }
    
    cJSON* tags = cJSON_GetObjectItem(event, "tags");
    if (!tags || !cJSON_IsArray(tags)) {
        return 0;
    }
    
    int hash_count = 0;
    cJSON* tag = NULL;
    cJSON_ArrayForEach(tag, tags) {
        if (hash_count >= max_hashes) break;
        if (!cJSON_IsArray(tag)) continue;
        
        cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
        if (!tag_name || !cJSON_IsString(tag_name)) continue;
        
        const char* tag_name_str = cJSON_GetStringValue(tag_name);
        if (tag_name_str && strcmp(tag_name_str, "x") == 0) {
            cJSON* hash_value = cJSON_GetArrayItem(tag, 1);
            if (hash_value && cJSON_IsString(hash_value)) {
                const char* hash = cJSON_GetStringValue(hash_value);
                if (hash && validate_sha256_format(hash)) {
                    strncpy(blob_hashes[hash_count], hash, 64);
                    blob_hashes[hash_count][64] = '\0';
                    hash_count++;
                }
            }
        }
    }
    
    return hash_count;
}

// Validate NIP-56 report types in x tags
int validate_report_types(cJSON* event) {
    if (!event) {
        return 0;
    }
    
    cJSON* tags = cJSON_GetObjectItem(event, "tags");
    if (!tags || !cJSON_IsArray(tags)) {
        return 0;
    }
    
    // Valid NIP-56 report types
    const char* valid_types[] = {
        "nudity", "malware", "profanity", "illegal",
        "spam", "impersonation", "other", NULL
    };
    
    cJSON* tag = NULL;
    cJSON_ArrayForEach(tag, tags) {
        if (!cJSON_IsArray(tag)) continue;
        
        cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
        if (!tag_name || !cJSON_IsString(tag_name)) continue;
        
        const char* tag_name_str = cJSON_GetStringValue(tag_name);
        if (tag_name_str && strcmp(tag_name_str, "x") == 0) {
            // Check if report type is provided and valid (optional)
            cJSON* report_type = cJSON_GetArrayItem(tag, 2);
            if (report_type && cJSON_IsString(report_type)) {
                const char* type_str = cJSON_GetStringValue(report_type);
                if (type_str) {
                    // Validate against known types (but allow unknown types per spec)
                    for (int i = 0; valid_types[i] != NULL; i++) {
                        if (strcmp(type_str, valid_types[i]) == 0) {
                            break;
                        }
                    }
                    // Note: Allow unknown types as per NIP-56 spec flexibility
                }
            }
        }
    }
    
    return 1; // Always return success - report types are informational
}

// Store blob report in database (optional server behavior)
int store_blob_report(const char* event_json, const char* reporter_pubkey) {
    // Optional implementation - servers MAY store reports
    sqlite3* db;
    sqlite3_stmt* stmt;
    int rc;
    
    rc = sqlite3_open_v2(g_db_path, &db, SQLITE_OPEN_READWRITE, NULL);
    if (rc) {
        return 0;
    }
    
    // Check if blob_reports table exists, create if not
    const char* create_table_sql =
        "CREATE TABLE IF NOT EXISTS blob_reports ("
        "id INTEGER PRIMARY KEY AUTOINCREMENT, "
        "report_event TEXT NOT NULL, "
        "reporter_pubkey TEXT, "
        "reported_at INTEGER NOT NULL, "
        "created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
        ")";
    
    rc = sqlite3_exec(db, create_table_sql, NULL, NULL, NULL);
    if (rc != SQLITE_OK) {
        sqlite3_close(db);
        return 0;
    }
    
    const char* sql = "INSERT INTO blob_reports (report_event, reporter_pubkey, reported_at) VALUES (?, ?, strftime('%s', 'now'))";
    
    rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
    if (rc != SQLITE_OK) {
        sqlite3_close(db);
        return 0;
    }
    
    sqlite3_bind_text(stmt, 1, event_json, -1, SQLITE_STATIC);
    sqlite3_bind_text(stmt, 2, reporter_pubkey, -1, SQLITE_STATIC);
    
    rc = sqlite3_step(stmt);
    int success = (rc == SQLITE_DONE);
    
    sqlite3_finalize(stmt);
    sqlite3_close(db);
    
    return success;
}

// Handle PUT /report requests (BUD-09)
void handle_report_request(void) {
    // Log the incoming request
    log_request("PUT", "/report", "pending", 0);
    
    // Validate HTTP method (only PUT allowed)
    const char* request_method = getenv("REQUEST_METHOD");
    if (!request_method || strcmp(request_method, "PUT") != 0) {
        send_error_response(405, "method_not_allowed", "Only PUT method allowed", "The /report endpoint only accepts PUT requests");
        log_request(request_method ? request_method : "NULL", "/report", "none", 405);
        return;
    }
    
    // Validate Content-Type
    const char* content_type = getenv("CONTENT_TYPE");
    if (!content_type || strstr(content_type, "application/json") == NULL) {
        send_error_response(415, "unsupported_media_type", "Content-Type must be application/json", "Report requests must be JSON");
        log_request("PUT", "/report", "none", 415);
        return;
    }
    
    // Validate Content-Length
    const char* content_length_str = getenv("CONTENT_LENGTH");
    if (!content_length_str) {
        send_error_response(400, "missing_content_length", "Content-Length header required", "Request body size must be specified");
        log_request("PUT", "/report", "none", 400);
        return;
    }
    
    long content_length = atol(content_length_str);
    if (content_length <= 0 || content_length > 10240) { // 10KB limit for report events
        send_error_response(400, "invalid_content_length", "Invalid content length", "Report events must be between 1 byte and 10KB");
        log_request("PUT", "/report", "none", 400);
        return;
    }
    
    // Read JSON request body
    char* json_body = malloc(content_length + 1);
    if (!json_body) {
        send_error_response(500, "memory_error", "Failed to allocate memory", "Internal server error");
        log_request("PUT", "/report", "none", 500);
        return;
    }
    
    size_t bytes_read = fread(json_body, 1, content_length, stdin);
    if (bytes_read != (size_t)content_length) {
        free(json_body);
        send_error_response(400, "incomplete_body", "Failed to read complete request body", "The request body was incomplete");
        log_request("PUT", "/report", "none", 400);
        return;
    }
    json_body[content_length] = '\0';
    
    // Parse JSON event
    cJSON* event = cJSON_Parse(json_body);
    if (!event) {
        free(json_body);
        send_error_response(400, "invalid_json", "Invalid JSON format", "Request body must be valid JSON");
        log_request("PUT", "/report", "none", 400);
        return;
    }
    
    // Validate event structure (NIP-56 kind 1984 with x tags)
    if (!validate_report_event_structure(event)) {
        cJSON_Delete(event);
        free(json_body);
        send_error_response(400, "invalid_report_event", "Invalid report event structure", "Report must be NIP-56 kind 1984 event with x tags");
        log_request("PUT", "/report", "none", 400);
        return;
    }
    
    // Validate nostr event signature and structure
    int structure_result = nostr_validate_event_structure(event);
    if (structure_result != NOSTR_SUCCESS) {
        cJSON_Delete(event);
        free(json_body);
        send_error_response(400, "invalid_event_structure", "Invalid nostr event structure", "Event does not conform to nostr event format");
        log_request("PUT", "/report", "structure_invalid", 400);
        return;
    }
    
    int crypto_result = nostr_verify_event_signature(event);
    if (crypto_result != NOSTR_SUCCESS) {
        cJSON_Delete(event);
        free(json_body);
        send_error_response(400, "invalid_signature", "Invalid event signature", "Event signature verification failed");
        log_request("PUT", "/report", "signature_invalid", 400);
        return;
    }
    
    // Extract blob hashes from x tags
    char blob_hashes[10][65]; // Support up to 10 blob hashes per report
    int hash_count = extract_blob_hashes_from_report(event, blob_hashes, 10);
    if (hash_count == 0) {
        cJSON_Delete(event);
        free(json_body);
        send_error_response(400, "no_blob_hashes", "No valid blob hashes found", "Report must contain at least one valid SHA-256 hash in x tags");
        log_request("PUT", "/report", "no_hashes", 400);
        return;
    }
    
    // Validate report types (optional validation)
    validate_report_types(event);
    
    // Extract reporter pubkey
    cJSON* pubkey_json = cJSON_GetObjectItem(event, "pubkey");
    const char* reporter_pubkey = NULL;
    if (pubkey_json && cJSON_IsString(pubkey_json)) {
        reporter_pubkey = cJSON_GetStringValue(pubkey_json);
    }
    
    // Optional: Store report in database (server behavior)
    if (reporter_pubkey) {
        store_blob_report(json_body, reporter_pubkey);
    }
    
    // Clean up
    cJSON_Delete(event);
    free(json_body);
    
    // Return success response
    printf("Status: 200 OK\r\n");
    printf("Content-Type: application/json\r\n\r\n");
    printf("{\n");
    printf("  \"message\": \"Report received\",\n");
    printf("  \"reported_blobs\": %d,\n", hash_count);
    printf("  \"reporter\": \"%s\"\n", reporter_pubkey ? reporter_pubkey : "anonymous");
    printf("}\n");
    
    log_request("PUT", "/report", reporter_pubkey ? "authenticated" : "anonymous", 200);
}