void test_KineticClient_Connect_should_return_KINETIC_STATUS_SESSION_EMPTY_upon_NULL_session_config(void)
{
    SessionHandle = 17;

    KineticStatus status = KineticClient_Connect(NULL, &SessionHandle);
    TEST_ASSERT_EQUAL_KineticStatus(KINETIC_STATUS_SESSION_EMPTY, status);
    TEST_ASSERT_EQUAL(KINETIC_HANDLE_INVALID, SessionHandle);
}
void setUp(void)
{
    KINETIC_CONNECTION_INIT(&Connection);
    Connection.connected = false; // Ensure gets set appropriately by internal connect call
    HmacKey = ByteArray_CreateWithCString("some hmac key");
    KINETIC_SESSION_INIT(&Session, "somehost.com", ClusterVersion, Identity, HmacKey);

    KineticConnection_NewConnection_ExpectAndReturn(&Session, DummyHandle);
    KineticConnection_FromHandle_ExpectAndReturn(DummyHandle, &Connection);
    KineticConnection_Connect_ExpectAndReturn(&Connection, KINETIC_STATUS_SUCCESS);

    KineticStatus status = KineticClient_Connect(&Session, &SessionHandle);
    TEST_ASSERT_EQUAL_KineticStatus(KINETIC_STATUS_SUCCESS, status);
    TEST_ASSERT_EQUAL(DummyHandle, SessionHandle);
}
static void ConnectSession(void)
{
    KINETIC_CONNECTION_INIT(&Connection);
    Connection.connected = false; // Ensure gets set appropriately by internal connect call
    Connection.connectionID = 12374626536; // Fake connection ID to allow connection to complete for these tests
    HmacKey = ByteArray_CreateWithCString("some hmac key");
    KINETIC_SESSION_INIT(&Session, "somehost.com", ClusterVersion, Identity, HmacKey);

    KineticConnection_NewConnection_ExpectAndReturn(&Session, DummyHandle);
    KineticConnection_FromHandle_ExpectAndReturn(DummyHandle, &Connection);
    KineticConnection_Connect_ExpectAndReturn(&Connection, KINETIC_STATUS_SUCCESS);
    // KineticConnection_ReceiveDeviceStatusMessage_ExpectAndReturn(&Connection, KINETIC_STATUS_SUCCESS);

    KineticStatus status = KineticClient_Connect(&Session, &SessionHandle);
    TEST_ASSERT_EQUAL_KineticStatus(KINETIC_STATUS_SUCCESS, status);
    TEST_ASSERT_EQUAL(DummyHandle, SessionHandle);
}
void test_KineticClient_Connect_should_return_KINETIC_STATUS_HOST_EMPTY_upon_NULL_host(void)
{
    ByteArray key = ByteArray_CreateWithCString("some_key");
    KineticSession config = {
        .host = "",
        .hmacKey = key,
    };
    SessionHandle = 17;

    KineticStatus status = KineticClient_Connect(&config, &SessionHandle);
    TEST_ASSERT_EQUAL_KineticStatus(KINETIC_STATUS_HOST_EMPTY, status);
    TEST_ASSERT_EQUAL(KINETIC_HANDLE_INVALID, SessionHandle);
}

void test_KineticClient_Connect_should_return_KINETIC_STATUS_HMAC_EMPTY_upon_NULL_HMAC_key(void)
{
    ByteArray key = {.len = 4, .data = NULL};
    KineticSession config = {
        .host = "somehost.com",
        .hmacKey = key,
    };
    SessionHandle = 17;

    KineticStatus status = KineticClient_Connect(&config, &SessionHandle);
    TEST_ASSERT_EQUAL_KineticStatus(KINETIC_STATUS_HMAC_EMPTY, status);
    TEST_ASSERT_EQUAL(KINETIC_HANDLE_INVALID, SessionHandle);
}

void test_KineticClient_Connect_should_return_false_upon_empty_HMAC_key(void)
{
    uint8_t keyData[] = {0, 1, 2, 3};
    ByteArray key = {.len = 0, .data = keyData};
    KineticSession config = {
        .host = "somehost.com",
        .hmacKey = key,
    };
    SessionHandle = 17;

    KineticStatus status = KineticClient_Connect(&config, &SessionHandle);
    TEST_ASSERT_EQUAL_KineticStatus(KINETIC_STATUS_HMAC_EMPTY, status);
    TEST_ASSERT_EQUAL(KINETIC_HANDLE_INVALID, SessionHandle);
}

void test_KineticClient_Connect_should_return_KINETIC_STATUS_SESSION_EMPTY_upon_NULL_handle(void)
{
    KineticSession config = {
        .host = "somehost.com",
        .hmacKey = ByteArray_CreateWithCString("some_key"),
    };
    SessionHandle = 17;

    KineticStatus status = KineticClient_Connect(&config, NULL);
    TEST_ASSERT_EQUAL_KineticStatus(KINETIC_STATUS_SESSION_EMPTY, status);
}

void test_KineticClient_Connect_should_return_KINETIC_STATUS_SESSION_INVALID_if_connection_for_handle_is_NULL(void)
{
    KineticSession config = {
        .host = "somehost.com",
        .hmacKey = ByteArray_CreateWithCString("some_key"),
    };
    SessionHandle = 17;

    KineticConnection_NewConnection_ExpectAndReturn(&config, DummyHandle);
    KineticConnection_FromHandle_ExpectAndReturn(DummyHandle, NULL);

    KineticStatus status = KineticClient_Connect(&config, &SessionHandle);
    TEST_ASSERT_EQUAL_KineticStatus(KINETIC_STATUS_CONNECTION_ERROR, status);
}

void test_KineticClient_Connect_should_return_status_from_a_failed_connection(void)
{
    KineticSession config = {
        .host = "somehost.com",
        .hmacKey = ByteArray_CreateWithCString("some_key"),
    };
    SessionHandle = 17;

    KineticConnection_NewConnection_ExpectAndReturn(&config, DummyHandle);
    KineticConnection_FromHandle_ExpectAndReturn(DummyHandle, &Connection);
    KineticConnection_Connect_ExpectAndReturn(&Connection, KINETIC_STATUS_HMAC_EMPTY);
    KineticConnection_FreeConnection_Expect(&SessionHandle);

    KineticStatus status = KineticClient_Connect(&config, &SessionHandle);
    TEST_ASSERT_EQUAL_KineticStatus(KINETIC_STATUS_HMAC_EMPTY, status);
    TEST_ASSERT_EQUAL(KINETIC_HANDLE_INVALID, SessionHandle);
}



void test_KineticClient_Disconnect_should_return_KINETIC_STATUS_CONNECTION_ERROR_upon_failure_to_get_connection_from_handle(void)
{
    SessionHandle = DummyHandle;
    KineticConnection_FromHandle_ExpectAndReturn(DummyHandle, NULL);
    KineticStatus status = KineticClient_Disconnect(&SessionHandle);
    TEST_ASSERT_EQUAL_KineticStatus(KINETIC_STATUS_CONNECTION_ERROR, status);
}

void test_KineticClient_Disconnect_should_disconnect_and_free_the_connection_associated_with_handle(void)
{
    SessionHandle = DummyHandle;
    KineticConnection_FromHandle_ExpectAndReturn(DummyHandle, &Connection);
    KineticConnection_Disconnect_ExpectAndReturn(&Connection, KINETIC_STATUS_SUCCESS);
    KineticConnection_FreeConnection_Expect(&SessionHandle);
    KineticStatus status = KineticClient_Disconnect(&SessionHandle);
    TEST_ASSERT_EQUAL_KineticStatus(KINETIC_STATUS_SUCCESS, status);
    TEST_ASSERT_EQUAL(KINETIC_HANDLE_INVALID, SessionHandle);
}

void test_KineticClient_Disconnect_should_return_status_from_KineticConnection_upon_faileure(void)
{
    SessionHandle = DummyHandle;
    KineticConnection_FromHandle_ExpectAndReturn(DummyHandle, &Connection);
    KineticConnection_Disconnect_ExpectAndReturn(&Connection, KINETIC_STATUS_SESSION_INVALID);
    KineticConnection_FreeConnection_Expect(&SessionHandle);
    KineticStatus status = KineticClient_Disconnect(&SessionHandle);
    TEST_ASSERT_EQUAL_KineticStatus(KINETIC_STATUS_SESSION_INVALID, status);
    TEST_ASSERT_EQUAL(KINETIC_HANDLE_INVALID, SessionHandle);
}
void test_kinetic_client_should_be_able_to_store_an_arbitrarily_large_binary_object_and_split_across_entries_via_ovelapped_IO_operations(void)
{
    const int maxIterations = 4;
    const int numCopiesToStore = 4;
    const KineticSession sessionConfig = {
        .host = "localhost",
        .port = KINETIC_PORT,
        .nonBlocking = false,
        .clusterVersion = 0,
        .identity = 1,
        .hmacKey = ByteArray_CreateWithCString("asdfasdf"),
    };

    for (int iteration = 0; iteration < maxIterations; iteration++) {

        printf("Overlapped PUT operation (iteration %d of %d)\n",
               iteration + 1, maxIterations);

        char* buf = malloc(sizeof(char) * BUFSIZE);
        int fd = open("test/support/data/test.data", O_RDONLY);
        int dataLen = read(fd, buf, BUFSIZE);
        close(fd);
        TEST_ASSERT_MESSAGE(dataLen > 0, "read error");

        /* thread structure */
        struct kinetic_thread_arg* kt_arg;
        pthread_t thread_id[KINETIC_MAX_THREADS];

        kinetic_client = malloc(sizeof(KineticSessionHandle) * numCopiesToStore);
        TEST_ASSERT_NOT_NULL_MESSAGE(kinetic_client, "kinetic_client malloc failed");

        kt_arg = malloc(sizeof(struct kinetic_thread_arg) * numCopiesToStore);
        TEST_ASSERT_NOT_NULL_MESSAGE(kt_arg, "kinetic_thread_arg malloc failed");

        for (int i = 0; i < numCopiesToStore; i++) {

            printf("    Overlapped PUT operations (writing copy %d of %d) on IP (iteration %d of %d):%s\n",
                   i + 1, numCopiesToStore, iteration + 1, maxIterations, sessionConfig.host);

            // Establish connection
            TEST_ASSERT_EQUAL_KineticStatus(
                KINETIC_STATUS_SUCCESS,
                KineticClient_Connect(&sessionConfig, &kinetic_client[i]));
            strcpy(kt_arg[i].ip, sessionConfig.host);

            // Configure the KineticEntry
            char keyPrefix[128];
            snprintf(keyPrefix, sizeof(keyPrefix), "pre_%02d%02d", iteration, i);
            LOGF("NEW HUNK PREFIX: %s", keyPrefix);
            ByteBuffer keyBuf = ByteBuffer_Create(kt_arg[i].key, sizeof(kt_arg[i].key));
            ByteBuffer_AppendCString(&keyBuf, keyPrefix);
            kt_arg[i].keyPrefixLength = keyBuf.bytesUsed;

            ByteBuffer verBuf = ByteBuffer_Create(kt_arg[i].version, sizeof(kt_arg[i].version));
            ByteBuffer_AppendCString(&verBuf, "v1.0");

            ByteBuffer tagBuf = ByteBuffer_Create(kt_arg[i].tag, sizeof(kt_arg[i].tag));
            ByteBuffer_AppendCString(&tagBuf, "some_value_tag...");

            ByteBuffer valBuf = ByteBuffer_Create(kt_arg[i].value, sizeof(kt_arg[i].value));
            
            kt_arg[i].entry = (KineticEntry) {
                .key = keyBuf,
                .newVersion = verBuf,
                .tag = tagBuf,
                .metadataOnly = false,
                .algorithm = KINETIC_ALGORITHM_SHA1,
                .value = valBuf,
            };

            // Create a ByteBuffer for consuming chunks of data out of for overlapped PUTs
            kt_arg[i].data = ByteBuffer_Create(buf, dataLen);

            // Spawn the worker thread
            kt_arg[i].sessionHandle = kinetic_client[i];
            int create_status = pthread_create(&thread_id[i], NULL, kinetic_put, &kt_arg[i]);
            TEST_ASSERT_EQUAL_MESSAGE(0, create_status, "pthread create failed");
        }

        // Wait for each overlapped PUT operations to complete and cleanup
        printf("  Waiting for PUT threads to exit...\n");
        for (int i = 0; i < numCopiesToStore; i++) {
            int join_status = pthread_join(thread_id[i], NULL);
            TEST_ASSERT_EQUAL_MESSAGE(0, join_status, "pthread join failed");
            KineticClient_Disconnect(&kinetic_client[i]);
        }

        // Cleanup the rest of the reources
        free(kinetic_client);
        free(kt_arg);
        free(buf);

        printf("  Iteration complete!\n");
    }

    printf("Overlapped PUT operation test complete!\n");
}