/*
 * NIP-04: Encrypted Direct Message Implementation
 * https://github.com/nostr-protocol/nips/blob/master/04.md
 */

#include "nip004.h"
#include "utils.h"
#include "nostr_common.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Forward declarations for crypto functions (private API)
// These functions are implemented in crypto/ but not exposed through public headers
int ecdh_shared_secret(const unsigned char* private_key, const unsigned char* public_key, unsigned char* shared_secret);
int nostr_secp256k1_get_random_bytes(unsigned char* buf, size_t len);

// AES context and functions for NIP-04 encryption
struct AES_ctx {
    unsigned char RoundKey[240];  // AES-256 key expansion
    unsigned char Iv[16];         // Initialization vector
};

void AES_init_ctx_iv(struct AES_ctx* ctx, const unsigned char* key, const unsigned char* iv);
void AES_CBC_encrypt_buffer(struct AES_ctx* ctx, unsigned char* buf, size_t length);
void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, unsigned char* buf, size_t length);

// Forward declarations for internal functions
static int aes_cbc_encrypt(const unsigned char* key, const unsigned char* iv,
                          const unsigned char* input, size_t input_len,
                          unsigned char* output);
static int aes_cbc_decrypt(const unsigned char* key, const unsigned char* iv,
                          const unsigned char* input, size_t input_len,
                          unsigned char* output);
static size_t pkcs7_pad(unsigned char* data, size_t data_len, size_t block_size);
static size_t pkcs7_unpad(unsigned char* data, size_t data_len);

// Memory clearing utility
static void memory_clear(const void *p, size_t len) {
    if (p && len) {
        memset((void *)p, 0, len);
    }
}

// =============================================================================
// AES-256-CBC ENCRYPTION/DECRYPTION USING TINYAES
// =============================================================================

static int aes_cbc_encrypt(const unsigned char* key, const unsigned char* iv,
                          const unsigned char* input, size_t input_len,
                          unsigned char* output) {
    if (!key || !iv || !input || !output || input_len % 16 != 0) {
        return -1;
    }
    
    // Initialize AES context with key and IV
    struct AES_ctx ctx;
    AES_init_ctx_iv(&ctx, key, iv);
    
    // Copy input to output (tinyAES works in-place)
    memcpy(output, input, input_len);
    
    // Encrypt using AES-256-CBC
    AES_CBC_encrypt_buffer(&ctx, output, input_len);
    
    return 0;
}

static int aes_cbc_decrypt(const unsigned char* key, const unsigned char* iv,
                          const unsigned char* input, size_t input_len,
                          unsigned char* output) {
    if (!key || !iv || !input || !output || input_len % 16 != 0) {
        return -1;
    }
    
    // Initialize AES context with key and IV
    struct AES_ctx ctx;
    AES_init_ctx_iv(&ctx, key, iv);
    
    // Copy input to output (tinyAES works in-place)
    memcpy(output, input, input_len);
    
    // Decrypt using AES-256-CBC
    AES_CBC_decrypt_buffer(&ctx, output, input_len);
    
    return 0;
}

// PKCS#7 padding functions
static size_t pkcs7_pad(unsigned char* data, size_t data_len, size_t block_size) {
    size_t padding = block_size - (data_len % block_size);
    for (size_t i = 0; i < padding; i++) {
        data[data_len + i] = (unsigned char)padding;
    }
    return data_len + padding;
}

static size_t pkcs7_unpad(unsigned char* data, size_t data_len) {
    if (data_len == 0) return 0;
    
    unsigned char padding = data[data_len - 1];
    if (padding == 0 || padding > 16) return 0; // Invalid padding
    
    // Verify padding
    for (size_t i = data_len - padding; i < data_len; i++) {
        if (data[i] != padding) return 0; // Invalid padding
    }
    
    return data_len - padding;
}

// =============================================================================
// NIP-04 IMPLEMENTATION
// =============================================================================

int nostr_nip04_encrypt(const unsigned char* sender_private_key,
                       const unsigned char* recipient_public_key, 
                       const char* plaintext,
                       char* output,
                       size_t output_size) {
    if (!sender_private_key || !recipient_public_key || !plaintext || !output) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    size_t plaintext_len = strlen(plaintext);
    
    if (plaintext_len > NOSTR_NIP04_MAX_PLAINTEXT_SIZE) {
        return NOSTR_ERROR_NIP04_BUFFER_TOO_SMALL;
    }
    
    // FIX: Calculate final size requirements EARLY before any allocations
    // CRITICAL: Account for PKCS#7 padding which ALWAYS adds 1-16 bytes
    // If plaintext_len is a multiple of 16, PKCS#7 adds a full 16-byte block
    size_t padded_len = ((plaintext_len / 16) + 1) * 16; // Always add one full block for PKCS#7
    size_t ciphertext_b64_max = ((padded_len + 2) / 3) * 4 + 1;
    size_t iv_b64_max = ((16 + 2) / 3) * 4 + 1;  // Always 25 bytes
    size_t estimated_result_len = ciphertext_b64_max + 4 + iv_b64_max;  // +4 for "?iv="
    
    // FIX: Check output buffer size BEFORE doing any work
    if (estimated_result_len > output_size) {
        return NOSTR_ERROR_NIP04_BUFFER_TOO_SMALL;
    }
    
    // Step 1: Compute ECDH shared secret
    unsigned char shared_secret[32];
    if (ecdh_shared_secret(sender_private_key, recipient_public_key, shared_secret) != 0) {
        return NOSTR_ERROR_CRYPTO_FAILED;
    }
    
    // Step 2: Generate random IV (16 bytes)
    unsigned char iv[16];
        if (nostr_secp256k1_get_random_bytes(iv, 16) != 1) {
        return NOSTR_ERROR_CRYPTO_FAILED;
    }
    
    // Step 3: Pad plaintext using PKCS#7
    unsigned char* padded_data = malloc(padded_len);
    if (!padded_data) {
        return NOSTR_ERROR_MEMORY_FAILED;
    }
    
    memcpy(padded_data, plaintext, plaintext_len);
    size_t actual_padded_len = pkcs7_pad(padded_data, plaintext_len, 16);
    
    // Step 4: Encrypt using AES-256-CBC
    unsigned char* ciphertext = malloc(padded_len);
    if (!ciphertext) {
        free(padded_data);
        return NOSTR_ERROR_MEMORY_FAILED;
    }
    
    if (aes_cbc_encrypt(shared_secret, iv, padded_data, actual_padded_len, ciphertext) != 0) {
        free(padded_data);
        free(ciphertext);
        return NOSTR_ERROR_CRYPTO_FAILED;
    }
    
    // Step 5: Base64 encode ciphertext and IV
    size_t ciphertext_b64_len = ((actual_padded_len + 2) / 3) * 4 + 1;
    size_t iv_b64_len = ((16 + 2) / 3) * 4 + 1;
    
    char* ciphertext_b64 = malloc(ciphertext_b64_len);
    char* iv_b64 = malloc(iv_b64_len);
    
    if (!ciphertext_b64 || !iv_b64) {
        free(padded_data);
        free(ciphertext);
        free(ciphertext_b64);
        free(iv_b64);
        return NOSTR_ERROR_MEMORY_FAILED;
    }
    
    // FIX: Pass buffer sizes to base64_encode and check for success
    size_t ct_b64_len = base64_encode(ciphertext, actual_padded_len, ciphertext_b64, ciphertext_b64_len);
    size_t iv_b64_len_actual = base64_encode(iv, 16, iv_b64, iv_b64_len);
    
    // FIX: Check if encoding succeeded
    if (ct_b64_len == 0 || iv_b64_len_actual == 0) {
        free(padded_data);
        free(ciphertext);
        free(ciphertext_b64);
        free(iv_b64);
        memory_clear(shared_secret, 32);
        return NOSTR_ERROR_CRYPTO_FAILED;
    }
    
    // Step 6: Format as "ciphertext?iv=iv_base64" - size check moved earlier, now guaranteed to fit
    size_t result_len = ct_b64_len + 4 + iv_b64_len_actual + 1; // +4 for "?iv=", +1 for null
    
    if (result_len > output_size) {
        free(padded_data);
        free(ciphertext);
        free(ciphertext_b64);
        free(iv_b64);
        memory_clear(shared_secret, 32);
        return NOSTR_ERROR_NIP04_BUFFER_TOO_SMALL;
    }
    
    snprintf(output, output_size, "%s?iv=%s", ciphertext_b64, iv_b64);
    
    // Cleanup
    memory_clear(shared_secret, 32);
    memory_clear(padded_data, padded_len);
    memory_clear(ciphertext, padded_len);
    free(padded_data);
    free(ciphertext);
    free(ciphertext_b64);
    free(iv_b64);
    
    return NOSTR_SUCCESS;
}

int nostr_nip04_decrypt(const unsigned char* recipient_private_key,
                       const unsigned char* sender_public_key,
                       const char* encrypted_data,
                       char* output,
                       size_t output_size) {
    if (!recipient_private_key || !sender_public_key || !encrypted_data || !output) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    // Step 1: Parse encrypted data format "ciphertext?iv=iv_base64"
    char* separator = strstr(encrypted_data, "?iv=");
    if (!separator) {
        return NOSTR_ERROR_NIP04_INVALID_FORMAT;
    }
    
    size_t ciphertext_b64_len = separator - encrypted_data;
    const char* iv_b64 = separator + 4; // Skip "?iv="
    
    if (ciphertext_b64_len == 0 || strlen(iv_b64) == 0) {
        return NOSTR_ERROR_NIP04_INVALID_FORMAT;
    }
    
    // Step 2: Create null-terminated copy of ciphertext base64
    char* ciphertext_b64 = malloc(ciphertext_b64_len + 1);
    if (!ciphertext_b64) {
        return NOSTR_ERROR_MEMORY_FAILED;
    }
    
    memcpy(ciphertext_b64, encrypted_data, ciphertext_b64_len);
    ciphertext_b64[ciphertext_b64_len] = '\0';
    
    // Step 3: Calculate proper buffer sizes for decoded data
    // Base64 decoding: 4 chars -> 3 bytes, so max decoded size is (len * 3) / 4
    size_t max_ciphertext_len = ((ciphertext_b64_len + 3) / 4) * 3;
    size_t max_iv_len = ((strlen(iv_b64) + 3) / 4) * 3;
    
    // Allocate buffers with proper sizes
    unsigned char* ciphertext = malloc(max_ciphertext_len);
    unsigned char* iv_buffer = malloc(max_iv_len);
    
    if (!ciphertext || !iv_buffer) {
        free(ciphertext_b64);
        free(ciphertext);
        free(iv_buffer);
        return NOSTR_ERROR_MEMORY_FAILED;
    }
    
    // Step 4: Base64 decode ciphertext and IV
    size_t ciphertext_len = base64_decode(ciphertext_b64, ciphertext);
    size_t iv_len = base64_decode(iv_b64, iv_buffer);
    
    if (ciphertext_len == 0 || iv_len != 16 || ciphertext_len % 16 != 0) {
        free(ciphertext_b64);
        free(ciphertext);
        free(iv_buffer);
        return NOSTR_ERROR_NIP04_INVALID_FORMAT;
    }
    
    // Copy IV to fixed-size buffer for safety
    unsigned char iv[16];
    memcpy(iv, iv_buffer, 16);
    free(iv_buffer);
    
    // Step 5: Compute ECDH shared secret
    unsigned char shared_secret[32];
    if (ecdh_shared_secret(recipient_private_key, sender_public_key, shared_secret) != 0) {
        free(ciphertext_b64);
        free(ciphertext);
        return NOSTR_ERROR_CRYPTO_FAILED;
    }
    
    // Step 6: Decrypt using AES-256-CBC
    unsigned char* plaintext_padded = malloc(ciphertext_len);
    if (!plaintext_padded) {
        free(ciphertext_b64);
        free(ciphertext);
        return NOSTR_ERROR_MEMORY_FAILED;
    }
    
    if (aes_cbc_decrypt(shared_secret, iv, ciphertext, ciphertext_len, plaintext_padded) != 0) {
        free(ciphertext_b64);
        free(ciphertext);
        free(plaintext_padded);
        return NOSTR_ERROR_NIP04_DECRYPT_FAILED;
    }
    
    // Step 7: Remove PKCS#7 padding
    size_t plaintext_len = pkcs7_unpad(plaintext_padded, ciphertext_len);
    if (plaintext_len == 0 || plaintext_len > ciphertext_len) {
        free(ciphertext_b64);
        free(ciphertext);
        free(plaintext_padded);
        return NOSTR_ERROR_NIP04_DECRYPT_FAILED;
    }
    
    // Step 8: Copy to output buffer and null-terminate
    if (plaintext_len + 1 > output_size) {
        free(ciphertext_b64);
        free(ciphertext);
        free(plaintext_padded);
        return NOSTR_ERROR_NIP04_BUFFER_TOO_SMALL;
    }
    
    memcpy(output, plaintext_padded, plaintext_len);
    output[plaintext_len] = '\0';
    
    // Cleanup
    memory_clear(shared_secret, 32);
    memory_clear(plaintext_padded, ciphertext_len);
    free(ciphertext_b64);
    free(ciphertext);
    free(plaintext_padded);
    
    return NOSTR_SUCCESS;
}
