/**
 * @file test_mvgal_transport.c
 * @brief Transport layer integration test
 *
 * Validates TCP transport lifecycle, localhost connect/disconnect,
 * message send/receive roundtrip, and stats reporting.
 */

#include "mvgal_transport.h"
#include "mvgal/mvgal_network.h"
#include "mvgal/mvgal_log.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

static int test_passed = 0;
static int test_failed = 0;

#define TEST_ASSERT(condition, message) \
    do { if (condition) { test_passed++; MVGAL_LOG_INFO("  \xe2\x9c\x93 %s", message); } \
         else { test_failed++; MVGAL_LOG_ERROR("  \xe2\x9c\x97 %s", message); } \
    } while (0)

/* ── Globals for loopback test ───────────────────────────────────────── */

static volatile int g_msg_received = 0;
static volatile int g_peer_callback_count = 0;
static uint64_t g_received_node_id = 0;

static void on_message(uint64_t node_id,
                       const mvgal_net_message_header_t *hdr,
                       const void *payload,
                       void *userdata)
{
    (void)userdata;
    g_msg_received = 1;
    g_received_node_id = node_id;

    /* Verify header fields are intact */
    if (hdr != NULL) {
        TEST_ASSERT(hdr->magic == MVGAL_NET_MAGIC, "Received message has valid magic");
        bool type_ok = (hdr->msg_type == MVGAL_NET_MSG_HEARTBEAT || 
                        hdr->msg_type == MVGAL_NET_MSG_CONFIG_SYNC);
        TEST_ASSERT(type_ok, "Received message has valid type");
    }
}

static void on_peer(uint64_t node_id,
                    mvgal_net_peer_state_t state,
                    void *userdata)
{
    (void)userdata;
    g_peer_callback_count++;
    MVGAL_LOG_INFO("  Peer event: node=%lu state=%d", (unsigned long)node_id, (int)state);
}

/* ── Tests ────────────────────────────────────────────────────────────── */

static void test_config_defaults(void)
{
    MVGAL_LOG_INFO("--- Config defaults ---");

    mvgal_transport_config_t cfg = MVGAL_TRANSPORT_DEFAULT_CONFIG;
    TEST_ASSERT(cfg.tcp_port == 0, "Default port is 0 (ephemeral)");
    TEST_ASSERT(cfg.bind_addr == NULL, "Default bind is NULL (INADDR_ANY)");
    TEST_ASSERT(cfg.heartbeat_interval_ms == 5000, "Default heartbeat 5000ms");
    TEST_ASSERT(cfg.timeout_ms == 30000, "Default timeout 30000ms");
    TEST_ASSERT(cfg.max_peers == 64, "Default max_peers 64");
    TEST_ASSERT(cfg.recv_buffer_size == 65536, "Default recv_buf 65536");
    TEST_ASSERT(cfg.enable_heartbeat == true, "Default heartbeat enabled");
}

static void test_init_shutdown(void)
{
    MVGAL_LOG_INFO("--- Init / shutdown ---");

    /* Default config */
    mvgal_transport_config_t cfg = MVGAL_TRANSPORT_DEFAULT_CONFIG;
    cfg.tcp_port = 0; /* ephemeral */
    cfg.bind_addr = "127.0.0.1";

    int rc = mvgal_transport_init(&cfg);
    TEST_ASSERT(rc == 0, "Transport init with default config");

    /* Double init should be safe (impl-defined, but should not crash) */
    rc = mvgal_transport_init(&cfg);
    TEST_ASSERT(rc == 0, "Transport re-init (idempotent)");

    mvgal_transport_shutdown();
    TEST_ASSERT(1, "Transport shutdown (no-op after re-init)");
}

static void test_listen_stop(void)
{
    MVGAL_LOG_INFO("--- Listen / stop ---");

    mvgal_transport_config_t cfg = MVGAL_TRANSPORT_DEFAULT_CONFIG;
    cfg.tcp_port = 0;
    cfg.bind_addr = "127.0.0.1";
    cfg.enable_heartbeat = false;

    TEST_ASSERT(mvgal_transport_init(&cfg) == 0, "Init for listen test");

    int rc = mvgal_transport_listen();
    TEST_ASSERT(rc == 0, "Listen on ephemeral port");

    /* Double listen should be safe */
    rc = mvgal_transport_listen();
    TEST_ASSERT(rc == 0, "Re-listen (idempotent)");

    rc = mvgal_transport_stop();
    TEST_ASSERT(rc == 0, "Stop listening");

    mvgal_transport_shutdown();
}

static void test_loopback_send_recv(void)
{
    MVGAL_LOG_INFO("--- Loopback send/recv ---");

    mvgal_transport_config_t cfg = MVGAL_TRANSPORT_DEFAULT_CONFIG;
    cfg.tcp_port = 17890; /* fixed port for test */
    cfg.bind_addr = "127.0.0.1";
    cfg.enable_heartbeat = false;
    cfg.timeout_ms = 5000;

    TEST_ASSERT(mvgal_transport_init(&cfg) == 0, "Init for loopback");

    mvgal_transport_set_msg_callback(on_message, NULL);
    mvgal_transport_set_peer_callback(on_peer, NULL);

    TEST_ASSERT(mvgal_transport_listen() == 0, "Listen on 127.0.0.1:17890");

    /* Connect to ourselves */
    uint64_t self_id = 42;
    int rc = mvgal_transport_connect("127.0.0.1", 17890, self_id);
    TEST_ASSERT(rc == 0, "Connect to localhost:17890");

    /* Poll briefly to let connection establish */
    for (int i = 0; i < 10 && !g_msg_received; i++) {
        mvgal_transport_poll(200);
    }

    /* Send a heartbeat message */
    mvgal_net_message_header_t hdr;
    memset(&hdr, 0, sizeof(hdr));
    hdr.magic = MVGAL_NET_MAGIC;
    hdr.version = MVGAL_NET_VERSION;
    hdr.msg_type = MVGAL_NET_MSG_HEARTBEAT;
    hdr.payload_size = 0;
    hdr.sequence_num = 1;
    hdr.flags = 0;

    rc = mvgal_transport_send(self_id, &hdr, NULL, 0);
    TEST_ASSERT(rc == 0, "Send heartbeat message");

    /* Poll to receive */
    for (int i = 0; i < 20 && !g_msg_received; i++) {
        mvgal_transport_poll(100);
    }
    TEST_ASSERT(g_msg_received, "Heartbeat received via callback");

    /* Check peer callback fired */
    TEST_ASSERT(g_peer_callback_count > 0, "Peer callback triggered");

    /* Stats */
    mvgal_transport_stats_t stats;
    memset(&stats, 0, sizeof(stats));
    mvgal_transport_get_stats(&stats);
    TEST_ASSERT(stats.messages_sent >= 1, "Stats: at least 1 message sent");
    TEST_ASSERT(stats.active_connections >= 1, "Stats: at least 1 active connection");

    /* Disconnect */
    rc = mvgal_transport_disconnect(self_id);
    TEST_ASSERT(rc == 0, "Disconnect peer");

    TEST_ASSERT(mvgal_transport_is_connected(self_id) == false, "Peer disconnected");

    mvgal_transport_stop();
    mvgal_transport_shutdown();
}

static void test_broadcast(void)
{
    MVGAL_LOG_INFO("--- Broadcast ---");

    mvgal_transport_config_t cfg = MVGAL_TRANSPORT_DEFAULT_CONFIG;
    cfg.tcp_port = 17891;
    cfg.bind_addr = "127.0.0.1";
    cfg.enable_heartbeat = false;
    cfg.timeout_ms = 3000;

    TEST_ASSERT(mvgal_transport_init(&cfg) == 0, "Init for broadcast");
    mvgal_transport_set_msg_callback(on_message, NULL);
    TEST_ASSERT(mvgal_transport_listen() == 0, "Listen for broadcast");

    /* Connect two "peers" to ourselves */
    TEST_ASSERT(mvgal_transport_connect("127.0.0.1", 17891, 101) == 0, "Connect peer 101");
    TEST_ASSERT(mvgal_transport_connect("127.0.0.1", 17891, 102) == 0, "Connect peer 102");

    /* Small wait for connections */
    for (int i = 0; i < 5; i++)
        mvgal_transport_poll(100);

    /* Broadcast */
    mvgal_net_message_header_t hdr;
    memset(&hdr, 0, sizeof(hdr));
    hdr.magic = MVGAL_NET_MAGIC;
    hdr.version = MVGAL_NET_VERSION;
    hdr.msg_type = MVGAL_NET_MSG_CONFIG_SYNC;
    hdr.payload_size = 0;
    hdr.sequence_num = 1;
    hdr.flags = 0;

    int rc = mvgal_transport_broadcast(&hdr, NULL, 0);
    TEST_ASSERT(rc == 0, "Broadcast to all peers");

    for (int i = 0; i < 10; i++)
        mvgal_transport_poll(100);

    TEST_ASSERT(g_msg_received, "Broadcast message received");

    mvgal_transport_disconnect_all();
    mvgal_transport_stop();
    mvgal_transport_shutdown();
}

static void test_multiple_connections(void)
{
    MVGAL_LOG_INFO("--- Multiple connections ---");

    mvgal_transport_config_t cfg = MVGAL_TRANSPORT_DEFAULT_CONFIG;
    cfg.tcp_port = 17892;
    cfg.bind_addr = "127.0.0.1";
    cfg.enable_heartbeat = false;
    cfg.max_peers = 16;
    cfg.timeout_ms = 3000;

    TEST_ASSERT(mvgal_transport_init(&cfg) == 0, "Init for multi-connect");
    mvgal_transport_set_peer_callback(on_peer, NULL);
    TEST_ASSERT(mvgal_transport_listen() == 0, "Listen for multi-connect");

    /* Connect 4 clients */
    for (int i = 0; i < 4; i++) {
        uint64_t nid = 200 + (uint64_t)i;
        int rc = mvgal_transport_connect("127.0.0.1", 17892, nid);
        char _msg[64]; snprintf(_msg, sizeof(_msg), "Connect peer %d", i);
        TEST_ASSERT(rc == 0, _msg);
    }

    for (int i = 0; i < 10; i++)
        mvgal_transport_poll(100);

    mvgal_transport_stats_t stats;
    mvgal_transport_get_stats(&stats);
    TEST_ASSERT(stats.active_connections >= 1, "Stats: active connections");

    /* Query peers */
    mvgal_net_peer_t peers[16];
    uint32_t count = 16;
    int rc = mvgal_transport_get_peers(peers, &count);
    TEST_ASSERT(rc == 0, "Get peers list");
    TEST_ASSERT(count > 0, "At least 1 peer returned");

    /* Verify connectivity check */
    for (uint32_t i = 0; i < count; i++) {
        char _msg2[64]; snprintf(_msg2, sizeof(_msg2), "Peer %lu is connected", (unsigned long)peers[i].node_id);
        bool connected = mvgal_transport_is_connected(peers[i].node_id);
        TEST_ASSERT(connected, _msg2);
    }

    mvgal_transport_disconnect_all();
    mvgal_transport_stop();
    mvgal_transport_shutdown();
}

static void test_invalid_connect(void)
{
    MVGAL_LOG_INFO("--- Invalid connect ---");

    mvgal_transport_config_t cfg = MVGAL_TRANSPORT_DEFAULT_CONFIG;
    TEST_ASSERT(mvgal_transport_init(&cfg) == 0, "Init for invalid connect");

    /* Connect to a port nobody listens on */
    int rc = mvgal_transport_connect("127.0.0.1", 19999, 999);
    if (rc == 0) {
        /* If it returned 0 due to EINPROGRESS, poll to see it fail */
        mvgal_transport_poll(100);
        if (!mvgal_transport_is_connected(999)) rc = -1;
    }
    TEST_ASSERT(rc != 0, "Connect to unreachable port fails");

    /* Connect to invalid address */
    rc = mvgal_transport_connect("256.256.256.256", 80, 998);
    TEST_ASSERT(rc != 0, "Connect to invalid address fails");

    mvgal_transport_shutdown();
}

int main(void)
{
    printf("\n============================================================\n");
    printf("  MVGAL Transport Layer Integration Test\n");
    printf("============================================================\n\n");

    test_config_defaults();
    test_init_shutdown();
    test_listen_stop();
    test_invalid_connect();
    test_loopback_send_recv();
    test_broadcast();
    test_multiple_connections();

    /* Summary */
    printf("\n============================================================\n");
    printf("  Test Summary\n");
    printf("============================================================\n");
    printf("  Passed: %d\n", test_passed);
    printf("  Failed: %d\n", test_failed);
    printf("  Total:  %d\n", test_passed + test_failed);
    printf("============================================================\n");

    if (test_failed == 0) {
        printf("\n\xe2\x9c\x93 Transport integration test PASSED\n\n");
    } else {
        printf("\n\xe2\x9c\x97 Transport integration test FAILED\n\n");
    }

    return (test_failed > 0) ? EXIT_FAILURE : EXIT_SUCCESS;
}
