/*
 * NOSTR Core Library - Static Linking Only Test
 * 
 * This test verifies that the library maintains its self-contained,
 * static-only design with no external cryptographic dependencies.
 * 
 * Test Categories:
 * 1. Library dependency analysis using ldd/otool
 * 2. Symbol resolution verification using nm/objdump  
 * 3. Build process validation
 * 4. Runtime independence verification
 * 5. Library size and content verification
 */

#define _GNU_SOURCE  // For popen/pclose on Linux
#include "../nostr_core/nostr_core.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include "../cjson/cJSON.h"

// ANSI color codes for output
#define GREEN   "\033[32m"
#define RED     "\033[31m" 
#define YELLOW  "\033[33m"
#define BLUE    "\033[34m"
#define RESET   "\033[0m"

// Test result tracking
static int tests_run = 0;
static int tests_passed = 0;

// Helper function to run shell commands and capture output
static int run_command(const char* command, char* output, size_t output_size) {
    FILE* fp = popen(command, "r");
    if (!fp) {
        return -1;
    }
    
    size_t total = 0;
    while (total < output_size - 1 && fgets(output + total, output_size - total, fp)) {
        total = strlen(output);
    }
    
    int status = pclose(fp);
    return WEXITSTATUS(status);
}

// Helper function to check if file exists
static int file_exists(const char* path) {
    struct stat st;
    return stat(path, &st) == 0;
}

// Test macro
#define RUN_TEST(test_name, test_func) do { \
    printf(BLUE "[TEST] " RESET "%s...\n", test_name); \
    tests_run++; \
    if (test_func()) { \
        printf(GREEN "[PASS] " RESET "%s\n\n", test_name); \
        tests_passed++; \
    } else { \
        printf(RED "[FAIL] " RESET "%s\n\n", test_name); \
    } \
} while(0)

// Test 1: Library Dependency Analysis
static int test_library_dependency_analysis(void) {
    char command[512];
    char output[4096];
    int result;
    
    // Check if we have the main library
    if (!file_exists("../libnostr_core.a")) {
        printf(RED "ERROR: " RESET "libnostr_core.a not found. Run 'make' first.\n");
        return 0;
    }
    
    // Create a simple test binary to analyze
    printf("Creating test binary for dependency analysis...\n");
    
    const char* test_code = 
        "#include \"nostr_core/nostr_core.h\"\n"
        "#include <stdio.h>\n"
        "int main() {\n"
        "    if (nostr_init() == NOSTR_SUCCESS) {\n"
        "        unsigned char privkey[32], pubkey[32];\n"
        "        if (nostr_generate_keypair(privkey, pubkey) == NOSTR_SUCCESS) {\n"
        "            printf(\"Crypto test passed\\n\");\n"
        "        }\n"
        "        nostr_cleanup();\n"
        "    }\n"
        "    return 0;\n"
        "}\n";
    
    FILE* fp = fopen("/tmp/static_test.c", "w");
    if (!fp) {
        printf(RED "ERROR: " RESET "Cannot create temporary test file\n");
        return 0;
    }
    fputs(test_code, fp);
    fclose(fp);
    
    // Compile the test binary
    snprintf(command, sizeof(command), 
        "gcc -I.. -Wall -Wextra -std=c99 /tmp/static_test.c -o /tmp/static_test ../libnostr_core.a -lm -static 2>/dev/null");
    
    result = system(command);
    if (result != 0) {
        printf(RED "ERROR: " RESET "Failed to compile test binary\n");
        return 0;
    }
    
    // Analyze dependencies with ldd (Linux) or otool (macOS)
    printf("Analyzing binary dependencies...\n");
    
#ifdef __linux__
    snprintf(command, sizeof(command), "ldd /tmp/static_test 2>&1");
#elif __APPLE__
    snprintf(command, sizeof(command), "otool -L /tmp/static_test 2>&1");
#else
    printf(YELLOW "WARNING: " RESET "Unknown platform, skipping dependency analysis\n");
    cleanup_and_return:
    unlink("/tmp/static_test.c");
    unlink("/tmp/static_test");
    return 1;
#endif
    
    result = run_command(command, output, sizeof(output));
    
    // Check for problematic dynamic dependencies (updated for OpenSSL migration)
    const char* forbidden_libs[] = {
        "libsecp256k1",  // Should be statically linked
        "libwally",      // Not used
        "libsodium"      // Not used
    };
    
    int found_forbidden = 0;
    for (int i = 0; i < 3; i++) {
        if (strstr(output, forbidden_libs[i])) {
            printf(RED "ERROR: " RESET "Found forbidden dynamic dependency: %s\n", forbidden_libs[i]);
            found_forbidden = 1;
        }
    }
    
    if (!found_forbidden) {
        printf(GREEN "GOOD: " RESET "No forbidden cryptographic dependencies found\n");
    }
    
    // For static binaries, ldd should say "not a dynamic executable" or show minimal deps
#ifdef __linux__ 
    if (strstr(output, "not a dynamic executable") || strstr(output, "statically linked")) {
        printf(GREEN "EXCELLENT: " RESET "Binary is statically linked\n");
    } else {
        printf(YELLOW "INFO: " RESET "Binary appears to have some dynamic dependencies:\n");
        printf("%s\n", output);
    }
#endif
    
    // Cleanup
    unlink("/tmp/static_test.c");
    unlink("/tmp/static_test");
    
    return !found_forbidden;
}

// Test 2: Symbol Resolution Verification
static int test_symbol_resolution_verification(void) {
    printf("Verifying secp256k1 symbols are present in static library...\n");
    
    // Use system() command instead of popen to avoid buffer issues
    int result = system("nm ../libnostr_core.a | grep -q secp256k1 2>/dev/null");
    
    if (result != 0) {
        printf(RED "ERROR: " RESET "No secp256k1 symbols found in library\n");
        return 0;
    }
    
    // Test individual symbols with specific commands
    const char* required_symbols[] = {
        "nostr_secp256k1_context_create",
        "nostr_secp256k1_ec_pubkey_create", 
        "nostr_secp256k1_schnorrsig_sign32",
        "nostr_secp256k1_schnorrsig_verify",
        "nostr_secp256k1_ecdh"
    };
    
    int symbols_found = 0;
    char command[256];
    
    for (int i = 0; i < 5; i++) {
        snprintf(command, sizeof(command), "nm ../libnostr_core.a | grep -q '%s' 2>/dev/null", required_symbols[i]);
        if (system(command) == 0) {
            symbols_found++;
            printf(GREEN "FOUND: " RESET "%s\n", required_symbols[i]);
        } else {
            printf(YELLOW "MISSING: " RESET "%s\n", required_symbols[i]);
        }
    }
    
    if (symbols_found >= 3) {
        printf(GREEN "GOOD: " RESET "Found %d/5 critical secp256k1 symbols\n", symbols_found);
        return 1;
    } else {
        printf(RED "ERROR: " RESET "Only found %d/5 critical secp256k1 symbols\n", symbols_found);
        return 0;
    }
}

// Test 3: Build Process Validation
static int test_build_process_validation(void) {
    char command[512];
    int result;
    
    printf("Testing minimal build requirements...\n");
    
    // Test that we can build with only libnostr_core.a and -lm
    const char* minimal_test = 
        "#include \"nostr_core/nostr_core.h\"\n"
        "int main() { return nostr_init() == NOSTR_SUCCESS ? 0 : 1; }\n";
    
    FILE* fp = fopen("/tmp/minimal_test.c", "w");
    if (!fp) return 0;
    fputs(minimal_test, fp);
    fclose(fp);
    
    // Try to build with minimal dependencies
    snprintf(command, sizeof(command), 
        "gcc -I.. -Wall -Wextra -std=c99 /tmp/minimal_test.c -o /tmp/minimal_test ../libnostr_core.a -lm 2>/dev/null");
    
    result = system(command);
    
    unlink("/tmp/minimal_test.c");
    
    if (result == 0) {
        printf(GREEN "EXCELLENT: " RESET "Can build with only libnostr_core.a and -lm\n");
        
        // Test that it actually runs
        result = system("/tmp/minimal_test");
        unlink("/tmp/minimal_test");
        
        if (result == 0) {
            printf(GREEN "EXCELLENT: " RESET "Minimal binary runs successfully\n");
            return 1;
        } else {
            printf(RED "ERROR: " RESET "Minimal binary failed to run\n");
            return 0;
        }
    } else {
        printf(RED "ERROR: " RESET "Cannot build with minimal dependencies\n");
        unlink("/tmp/minimal_test");
        return 0;
    }
}

// Test 4: Runtime Independence Test
static int test_runtime_independence(void) {
    printf("Testing runtime independence (crypto functionality)...\n");
    
    // Initialize the library
    if (nostr_init() != NOSTR_SUCCESS) {
        printf(RED "ERROR: " RESET "Library initialization failed\n");
        return 0;
    }
    
    // Test key generation
    unsigned char private_key[32];
    unsigned char public_key[32];
    
    if (nostr_generate_keypair(private_key, public_key) != NOSTR_SUCCESS) {
        printf(RED "ERROR: " RESET "Key generation failed\n");
        nostr_cleanup();
        return 0;
    }
    printf(GREEN "GOOD: " RESET "Key generation works\n");
    
    // Test bech32 encoding
    char nsec[100], npub[100];
    if (nostr_key_to_bech32(private_key, "nsec", nsec) != NOSTR_SUCCESS ||
        nostr_key_to_bech32(public_key, "npub", npub) != NOSTR_SUCCESS) {
        printf(RED "ERROR: " RESET "Bech32 encoding failed\n");
        nostr_cleanup();
        return 0;
    }
    printf(GREEN "GOOD: " RESET "Bech32 encoding works\n");
    
    // Test signing
    cJSON* event = nostr_create_and_sign_event(1, "Test message", NULL, private_key, 0);
    if (!event) {
        printf(RED "ERROR: " RESET "Event creation/signing failed\n");
        nostr_cleanup();
        return 0;
    }
    printf(GREEN "GOOD: " RESET "Event signing works\n");
    cJSON_Delete(event);
    
    // Test NIP-44 encryption if available
    char plaintext[] = "Hello, NOSTR!";
    char encrypted[1024];
    char decrypted[1024];
    
    // Generate recipient keys
    unsigned char recipient_private[32], recipient_public[32];
    nostr_generate_keypair(recipient_private, recipient_public);
    
    if (nostr_nip44_encrypt(private_key, recipient_public, plaintext, encrypted, sizeof(encrypted)) == NOSTR_SUCCESS) {
        if (nostr_nip44_decrypt(recipient_private, public_key, encrypted, decrypted, sizeof(decrypted)) == NOSTR_SUCCESS) {
            if (strcmp(plaintext, decrypted) == 0) {
                printf(GREEN "EXCELLENT: " RESET "NIP-44 encryption/decryption works\n");
            } else {
                printf(YELLOW "WARNING: " RESET "NIP-44 decryption mismatch\n");
            }
        } else {
            printf(YELLOW "WARNING: " RESET "NIP-44 decryption failed\n");
        }
    } else {
        printf(YELLOW "WARNING: " RESET "NIP-44 encryption failed (may not be enabled)\n");
    }
    
    nostr_cleanup();
    return 1;
}

// Test 5: Library Size and Content Verification
static int test_library_size_and_content(void) {
    struct stat st;
    char command[512];
    char output[4096];
    
    printf("Verifying library size and content...\n");
    
    // Check library size
    if (stat("../libnostr_core.a", &st) != 0) {
        printf(RED "ERROR: " RESET "Cannot stat libnostr_core.a\n");
        return 0;
    }
    
    size_t lib_size = st.st_size;
    printf("Library size: %zu bytes (%.2f MB)\n", lib_size, lib_size / 1024.0 / 1024.0);
    
    // Expect "fat" library to be at least 1MB (with secp256k1 bundled)
    if (lib_size < 1024 * 1024) {
        printf(YELLOW "WARNING: " RESET "Library seems small (%.2f MB). May not include secp256k1.\n", 
               lib_size / 1024.0 / 1024.0);
    } else {
        printf(GREEN "GOOD: " RESET "Library size suggests secp256k1 is bundled\n");
    }
    
    // List archive contents
    snprintf(command, sizeof(command), "ar -t ../libnostr_core.a | wc -l");
    if (run_command(command, output, sizeof(output)) == 0) {
        int object_count = atoi(output);
        printf("Archive contains %d object files\n", object_count);
        
        if (object_count > 20) {
            printf(GREEN "EXCELLENT: " RESET "High object count suggests secp256k1 objects included\n");
        } else {
            printf(YELLOW "WARNING: " RESET "Low object count (%d). secp256k1 may not be fully bundled\n", object_count);
        }
    }
    
    // Check for secp256k1-specific object files
    snprintf(command, sizeof(command), "ar -t ../libnostr_core.a | grep -E '(secp256k1|ecmult)' | head -5");
    if (run_command(command, output, sizeof(output)) == 0 && strlen(output) > 0) {
        printf(GREEN "EXCELLENT: " RESET "Found secp256k1 object files in archive:\n");
        printf("%s", output);
    } else {
        printf(YELLOW "WARNING: " RESET "No obvious secp256k1 object files found\n");
    }
    
    return 1;
}

// Main test runner
int main(int argc, char* argv[]) {
    (void)argc;
    (void)argv;
    
    printf(BLUE "NOSTR Core Library - Static Linking Only Test\n");
    printf("==============================================" RESET "\n\n");
    
    printf("This test verifies that the library maintains its self-contained,\n");
    printf("static-only design with no external cryptographic dependencies.\n\n");
    
    // Run all tests
    RUN_TEST("Library Dependency Analysis", test_library_dependency_analysis);
    RUN_TEST("Symbol Resolution Verification", test_symbol_resolution_verification); 
    RUN_TEST("Build Process Validation", test_build_process_validation);
    RUN_TEST("Runtime Independence Test", test_runtime_independence);
    RUN_TEST("Library Size and Content Verification", test_library_size_and_content);
    
    // Print summary
    printf(BLUE "============================================\n");
    printf("TEST SUMMARY\n");
    printf("============================================" RESET "\n");
    printf("Tests run: %d\n", tests_run);
    printf("Tests passed: %d\n", tests_passed);
    
    if (tests_passed == tests_run) {
        printf(GREEN "ALL TESTS PASSED!" RESET "\n");
        printf("✅ Library maintains static-only design\n");
        printf("✅ No external crypto dependencies\n");
        printf("✅ Self-contained and portable\n");
    } else {
        printf(RED "SOME TESTS FAILED!" RESET "\n");
        printf("❌ %d out of %d tests failed\n", tests_run - tests_passed, tests_run);
        printf("⚠️  Library may have external dependencies or missing components\n");
    }
    
    return (tests_passed == tests_run) ? 0 : 1;
}
