// Kyler Olsen
// YREA SLS
// Hash Table Tests
// November 2025

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>

#include "sls/string.h"
#include "sls/hash_table.h"
#include "tests/tests.h"

static const size_t NUM_HASH_TABLE_TESTS = 12;

static TestResult pass_hash_table_test(SlsStr test_name) {
    TestResult result = {.name = test_name, .status = TEST_NOT_IMPLEMENTED};
    result.status = TEST_PASS;
    return result;
}

static TestResult fail_hash_table_test(SlsStr test_name, SlsStr message) {
    TestResult result = { .name = test_name, .status = TEST_NOT_IMPLEMENTED };
    result.status = TEST_ERROR;
    result.message = sls_str_cpy(message);

    if (result.message.str == NULL) {
        result.error = (SlsError){ SLS_STR("Out Of Memory Error."), 1 };
        result.status = TEST_ERROR;
    }
    return result;
}

// Test init_hash_table and del_hash_table
static TestResult test_init_and_delete() {
    HashTable *ht = init_hash_table(10);
    if (!ht) return fail_hash_table_test(SLS_STR("test_init_and_delete"), SLS_STR("Initialization failed"));
    
    if (ht->buckets_count < 10) {
        del_hash_table(ht);
        return fail_hash_table_test(SLS_STR("test_init_and_delete"), SLS_STR("Bucket count less than minimum"));
    }

    del_hash_table(ht);
    return pass_hash_table_test(SLS_STR("test_init_and_delete"));
}

// Test basic put and get operations
static TestResult test_put_and_get() {
    HashTable *ht = init_hash_table(10);
    if (!ht) return fail_hash_table_test(SLS_STR("test_put_and_get"), SLS_STR("Initialization failed"));

    int value1 = 42;
    int value2 = 84;
    SlsStr key1 = SLS_STR("key1");
    SlsStr key2 = SLS_STR("key2");

    if (!hash_table_put(ht, key1, &value1)) {
        del_hash_table(ht);
        return fail_hash_table_test(SLS_STR("test_put_and_get"), SLS_STR("Put operation failed"));
    }

    if (!hash_table_put(ht, key2, &value2)) {
        del_hash_table(ht);
        return fail_hash_table_test(SLS_STR("test_put_and_get"), SLS_STR("Second put operation failed"));
    }

    int *retrieved1 = (int*)hash_table_get(ht, key1, NULL);
    int *retrieved2 = (int*)hash_table_get(ht, key2, NULL);

    if (!retrieved1 || *retrieved1 != 42) {
        del_hash_table(ht);
        return fail_hash_table_test(SLS_STR("test_put_and_get"), SLS_STR("Get operation failed for key1"));
    }

    if (!retrieved2 || *retrieved2 != 84) {
        del_hash_table(ht);
        return fail_hash_table_test(SLS_STR("test_put_and_get"), SLS_STR("Get operation failed for key2"));
    }

    del_hash_table(ht);
    return pass_hash_table_test(SLS_STR("test_put_and_get"));
}

// Test get with non-existent key returns default
static TestResult test_get_nonexistent_key() {
    HashTable *ht = init_hash_table(10);
    if (!ht) return fail_hash_table_test(SLS_STR("test_get_nonexistent_key"), SLS_STR("Initialization failed"));

    int default_value = -1;
    SlsStr key = SLS_STR("nonexistent");

    int *result = (int*)hash_table_get(ht, key, &default_value);
    if (result != &default_value) {
        del_hash_table(ht);
        return fail_hash_table_test(SLS_STR("test_get_nonexistent_key"), SLS_STR("Did not return default value"));
    }

    del_hash_table(ht);
    return pass_hash_table_test(SLS_STR("test_get_nonexistent_key"));
}

// Test updating existing key
static TestResult test_update_existing_key() {
    HashTable *ht = init_hash_table(10);
    if (!ht) return fail_hash_table_test(SLS_STR("test_update_existing_key"), SLS_STR("Initialization failed"));

    int value1 = 100;
    int value2 = 200;
    SlsStr key = SLS_STR("update_key");

    hash_table_put(ht, key, &value1);
    hash_table_put(ht, key, &value2);

    int *result = (int*)hash_table_get(ht, key, NULL);
    if (!result || *result != 200) {
        del_hash_table(ht);
        return fail_hash_table_test(SLS_STR("test_update_existing_key"), SLS_STR("Update failed"));
    }

    del_hash_table(ht);
    return pass_hash_table_test(SLS_STR("test_update_existing_key"));
}

// Test delete operation
static TestResult test_delete_key() {
    HashTable *ht = init_hash_table(10);
    if (!ht) return fail_hash_table_test(SLS_STR("test_delete_key"), SLS_STR("Initialization failed"));

    int value = 42;
    SlsStr key = SLS_STR("delete_key");

    hash_table_put(ht, key, &value);

    if (!hash_table_del(ht, key)) {
        del_hash_table(ht);
        return fail_hash_table_test(SLS_STR("test_delete_key"), SLS_STR("Delete returned FALSE"));
    }

    int default_val = -1;
    int *result = (int*)hash_table_get(ht, key, &default_val);
    if (result != &default_val) {
        del_hash_table(ht);
        return fail_hash_table_test(SLS_STR("test_delete_key"), SLS_STR("Key still exists after delete"));
    }

    del_hash_table(ht);
    return pass_hash_table_test(SLS_STR("test_delete_key"));
}

// Test delete non-existent key returns FALSE
static TestResult test_delete_nonexistent_key() {
    HashTable *ht = init_hash_table(10);
    if (!ht) return fail_hash_table_test(SLS_STR("test_delete_nonexistent_key"), SLS_STR("Initialization failed"));

    SlsStr key = SLS_STR("nonexistent");

    if (hash_table_del(ht, key)) {
        del_hash_table(ht);
        return fail_hash_table_test(SLS_STR("test_delete_nonexistent_key"), SLS_STR("Delete returned TRUE for nonexistent key"));
    }

    del_hash_table(ht);
    return pass_hash_table_test(SLS_STR("test_delete_nonexistent_key"));
}

// Test collision handling (multiple keys in same bucket)
static TestResult test_collision_handling() {
    HashTable *ht = init_hash_table(1); // Force collisions with single bucket
    if (!ht) return fail_hash_table_test(SLS_STR("test_collision_handling"), SLS_STR("Initialization failed"));

    int val1 = 1, val2 = 2, val3 = 3;
    SlsStr key1 = SLS_STR("key1");
    SlsStr key2 = SLS_STR("key2");
    SlsStr key3 = SLS_STR("key3");

    hash_table_put(ht, key1, &val1);
    hash_table_put(ht, key2, &val2);
    hash_table_put(ht, key3, &val3);

    int *r1 = (int*)hash_table_get(ht, key1, NULL);
    int *r2 = (int*)hash_table_get(ht, key2, NULL);
    int *r3 = (int*)hash_table_get(ht, key3, NULL);

    if (!r1 || *r1 != 1 || !r2 || *r2 != 2 || !r3 || *r3 != 3) {
        del_hash_table(ht);
        return fail_hash_table_test(SLS_STR("test_collision_handling"), SLS_STR("Collision handling failed"));
    }

    del_hash_table(ht);
    return pass_hash_table_test(SLS_STR("test_collision_handling"));
}

// Test empty string key
static TestResult test_empty_key() {
    HashTable *ht = init_hash_table(10);
    if (!ht) return fail_hash_table_test(SLS_STR("test_empty_key"), SLS_STR("Initialization failed"));

    int value = 99;
    SlsStr key = SLS_STR("");

    if (!hash_table_put(ht, key, &value)) {
        del_hash_table(ht);
        return fail_hash_table_test(SLS_STR("test_empty_key"), SLS_STR("Put with empty key failed"));
    }

    int *result = (int*)hash_table_get(ht, key, NULL);
    if (!result || *result != 99) {
        del_hash_table(ht);
        return fail_hash_table_test(SLS_STR("test_empty_key"), SLS_STR("Get with empty key failed"));
    }

    del_hash_table(ht);
    return pass_hash_table_test(SLS_STR("test_empty_key"));
}

// Test long key
static TestResult test_long_key() {
    HashTable *ht = init_hash_table(10);
    if (!ht) return fail_hash_table_test(SLS_STR("test_long_key"), SLS_STR("Initialization failed"));

    char long_key_str[256];
    for (size_t i = 0; i < 255; i++) long_key_str[i] = 'A';
    long_key_str[255] = '\0';

    SlsStr key = sls_str_malloc(long_key_str, 255);
    int value = 777;

    if (!hash_table_put(ht, key, &value)) {
        sls_str_free(&key);
        del_hash_table(ht);
        return fail_hash_table_test(SLS_STR("test_long_key"), SLS_STR("Put with long key failed"));
    }

    int *result = (int*)hash_table_get(ht, key, NULL);
    if (!result || *result != 777) {
        sls_str_free(&key);
        del_hash_table(ht);
        return fail_hash_table_test(SLS_STR("test_long_key"), SLS_STR("Get with long key failed"));
    }

    sls_str_free(&key);
    del_hash_table(ht);
    return pass_hash_table_test(SLS_STR("test_long_key"));
}

// Test multiple operations sequence
static TestResult test_multiple_operations() {
    HashTable *ht = init_hash_table(10);
    if (!ht) return fail_hash_table_test(SLS_STR("test_multiple_operations"), SLS_STR("Initialization failed"));

    int vals[5] = {10, 20, 30, 40, 50};
    SlsStr keys[5] = {SLS_STR("k1"), SLS_STR("k2"), SLS_STR("k3"), SLS_STR("k4"), SLS_STR("k5")};

    // Insert all
    for (int i = 0; i < 5; i++) {
        if (!hash_table_put(ht, keys[i], &vals[i])) {
            del_hash_table(ht);
            return fail_hash_table_test(SLS_STR("test_multiple_operations"), SLS_STR("Insert failed"));
        }
    }

    // Delete some
    hash_table_del(ht, keys[1]);
    hash_table_del(ht, keys[3]);

    // Verify remaining
    int *r0 = (int*)hash_table_get(ht, keys[0], NULL);
    int *r2 = (int*)hash_table_get(ht, keys[2], NULL);
    int *r4 = (int*)hash_table_get(ht, keys[4], NULL);

    if (!r0 || *r0 != 10 || !r2 || *r2 != 30 || !r4 || *r4 != 50) {
        del_hash_table(ht);
        return fail_hash_table_test(SLS_STR("test_multiple_operations"), SLS_STR("Get after delete failed"));
    }

    // Verify deleted
    int def = -1;
    int *r1 = (int*)hash_table_get(ht, keys[1], &def);
    int *r3 = (int*)hash_table_get(ht, keys[3], &def);

    if (r1 != &def || r3 != &def) {
        del_hash_table(ht);
        return fail_hash_table_test(SLS_STR("test_multiple_operations"), SLS_STR("Deleted keys still present"));
    }

    del_hash_table(ht);
    return pass_hash_table_test(SLS_STR("test_multiple_operations"));
}

// Test storing NULL pointers
static TestResult test_null_value() {
    HashTable *ht = init_hash_table(10);
    if (!ht) return fail_hash_table_test(SLS_STR("test_null_value"), SLS_STR("Initialization failed"));

    SlsStr key = SLS_STR("null_key");
    
    if (!hash_table_put(ht, key, NULL)) {
        del_hash_table(ht);
        return fail_hash_table_test(SLS_STR("test_null_value"), SLS_STR("Put with NULL value failed"));
    }

    int default_val = -1;
    void *result = hash_table_get(ht, key, &default_val);
    
    // Should retrieve NULL, not default
    if (result == &default_val) {
        del_hash_table(ht);
        return fail_hash_table_test(SLS_STR("test_null_value"), SLS_STR("NULL value not stored correctly"));
    }

    del_hash_table(ht);
    return pass_hash_table_test(SLS_STR("test_null_value"));
}

// Test large number of entries
static TestResult test_large_capacity() {
    HashTable *ht = init_hash_table(10);
    if (!ht) return fail_hash_table_test(SLS_STR("test_large_capacity"), SLS_STR("Initialization failed"));

    const size_t COUNT = 100;
    int *values = malloc(sizeof(int) * COUNT);
    SlsStr *keys = malloc(sizeof(SlsStr) * COUNT);

    for (size_t i = 0; i < COUNT; i++) {
        values[i] = (int)i;
        char key_buf[32];
        snprintf(key_buf, 32, "key_%zu", i);
        keys[i] = sls_str_malloc(key_buf, strlen(key_buf));
        
        if (!hash_table_put(ht, keys[i], &values[i])) {
            for (size_t j = 0; j <= i; j++) sls_str_free(&keys[j]);
            free(keys);
            free(values);
            del_hash_table(ht);
            return fail_hash_table_test(SLS_STR("test_large_capacity"), SLS_STR("Put failed in bulk insert"));
        }
    }

    // Verify all entries
    for (size_t i = 0; i < COUNT; i++) {
        int *result = (int*)hash_table_get(ht, keys[i], NULL);
        if (!result || *result != (int)i) {
            for (size_t j = 0; j < COUNT; j++) sls_str_free(&keys[j]);
            free(keys);
            free(values);
            del_hash_table(ht);
            return fail_hash_table_test(SLS_STR("test_large_capacity"), SLS_STR("Get failed in bulk verify"));
        }
    }

    for (size_t i = 0; i < COUNT; i++) sls_str_free(&keys[i]);
    free(keys);
    free(values);
    del_hash_table(ht);
    return pass_hash_table_test(SLS_STR("test_large_capacity"));
}

// Run all hash table tests
TestsReport run_hash_table_tests() {
    TestsReport report = {
        .section = SLS_STR("hash_table_tests"),
        .count = NUM_HASH_TABLE_TESTS,
        .tests = malloc(sizeof(TestResult) * NUM_HASH_TABLE_TESTS)
    };

    size_t i = 0;
    report.tests[i++] = test_init_and_delete();
    report.tests[i++] = test_put_and_get();
    report.tests[i++] = test_get_nonexistent_key();
    report.tests[i++] = test_update_existing_key();
    report.tests[i++] = test_delete_key();
    report.tests[i++] = test_delete_nonexistent_key();
    report.tests[i++] = test_collision_handling();
    report.tests[i++] = test_empty_key();
    report.tests[i++] = test_long_key();
    report.tests[i++] = test_multiple_operations();
    report.tests[i++] = test_null_value();
    report.tests[i++] = test_large_capacity();
    
    return report;
}
