/**
 * @file hal_websocket.c
 * @brief HAL WebSocket 实现（JieLi 平台）
 */

#include "aiagent_defaults.h"

#ifdef WS_IMPL_JIELI

#include "hal_websocket.h"
#include "hal_log.h"
#include "os/os_api.h"
#include "system/includes.h"
#include "web_socket/websocket_api.h"
#include <stdlib.h>
#include <string.h>

#define TAG  "HAL_WS"

#define PING_INTERVAL_MS   3000   /* ping 发送间隔 */
#define PING_TIMEOUT_MS    10000  /* 无响应超时判定 */
#define PING_THREAD_STACK  512
#define PING_POLL_DLY      50     /* 轮询周期 (os_time_dly 单位 10ms) */
#define PING_FAIL_TOLERATE 3      /* 连续失败阈值 */
#define RECV_TIMEOUT_MS    100    /* 接收线程单次 recv 超时，限制持锁时长 */

typedef struct {
    char                   *url;
    hal_ws_callback_t       callback;
    void                   *user_ctx;
    size_t                  recv_buf_size;
    struct websocket_struct  ws_info;
    OS_MUTEX                mutex;
    volatile bool           connected;
    volatile bool           running;
    volatile bool           disconnect_notified;
    volatile u32            last_recv_ms;
    volatile u32            last_ping_ms;
    volatile u32            last_pong_ms;   /* 最近一次收到 PONG 的时刻，与 last_ping_ms 配对判定 */
    volatile bool           ping_running;
} hal_ws_impl_t;

/* 回调转发用全局指针 */
static hal_ws_impl_t *g_ws_impl = NULL;

/* 前向声明 */
static void notify_disconnect_once(hal_ws_impl_t *impl);

/* 自定义心跳线程 */
static void ping_thread_entry(void *param)
{
    hal_ws_impl_t *impl = (hal_ws_impl_t *)param;
    if (!impl) {
        return;
    }

    int consecutive_fail = 0;

    while (impl->ping_running && impl->connected) {
        os_time_dly(PING_POLL_DLY);
        if (!impl->ping_running || !impl->connected) {
            break;
        }

        u32 now = timer_get_ms();

        /* PING/PONG 闭环判定连接状态。 */
        if (impl->last_ping_ms > impl->last_pong_ms) {
            u32 unanswered = now - impl->last_ping_ms;
            if (unanswered >= PING_TIMEOUT_MS) {
                BD_LOGW(TAG, "ping unanswered (%dms), disconnect", unanswered);
                impl->ping_running = false;
                notify_disconnect_once(impl);
                break;
            }
        }

        if ((now - impl->last_ping_ms) < PING_INTERVAL_MS) {
            continue;
        }

        /* PING 使用 4 字节载荷 */
        u8 ping_payload[4];
        ping_payload[0] = (u8)(now >> 24);
        ping_payload[1] = (u8)(now >> 16);
        ping_payload[2] = (u8)(now >> 8);
        ping_payload[3] = (u8)(now);

        os_mutex_pend(&impl->mutex, 0);
        int ret = impl->ws_info._send(
            &impl->ws_info, ping_payload, sizeof(ping_payload), WCT_PING);
        os_mutex_post(&impl->mutex);

        /* 仅连续发送失败达到阈值时上报断连 */
        if (ret < 0) {
            consecutive_fail++;
            BD_LOGW(TAG, "ping send fail (ret=%d, %d/%d)",
                    ret, consecutive_fail, PING_FAIL_TOLERATE);
            if (consecutive_fail >= PING_FAIL_TOLERATE) {
                impl->ping_running = false;
                notify_disconnect_once(impl);
                break;
            }
        } else {
            consecutive_fail = 0;
            /* 上次 PING 已收到 PONG 后，再刷新本次 PING 时间。 */
            if (impl->last_ping_ms <= impl->last_pong_ms) {
                impl->last_ping_ms = now;
            }
        }
    }
}

/* 统一上报断连事件 */
static void notify_disconnect_once(hal_ws_impl_t *impl)
{
    if (!impl) {
        return;
    }
    if (impl->disconnect_notified) {
        return;
    }
    impl->disconnect_notified = true;
    impl->connected = false;
    if (impl->callback) {
        impl->callback(HAL_WS_EVENT_DISCONNECTED, NULL, 0, impl->user_ctx);
    }
}

/* 接收线程与发送流程共用互斥锁，串行访问 WebSocket 连接。 */
static void recv_thread_entry(void *param)
{
    hal_ws_impl_t *impl = (hal_ws_impl_t *)param;
    if (!impl) {
        return;
    }

    while (impl->running) {
        os_mutex_pend(&impl->mutex, 0);
        int ret = websockets_client_socket_recv(&impl->ws_info);
        os_mutex_post(&impl->mutex);

        if (ret < 0) {
            notify_disconnect_once(impl);
            break;
        }
        os_time_dly(1);   /* 锁外让出调度 */
    }
}

static void ws_data_callback(u8 *pkg_data, u32 pkg_len, u8 type)
{
    hal_ws_impl_t *impl = g_ws_impl;
    if (!impl || !impl->callback) {
        return;
    }

    /* 收到任何数据都重置超时计时器 */
    impl->last_recv_ms = timer_get_ms();

    /* type 高位含 FIN 标志(0x80)，低4位是数据类型 */
    u8 data_type = type & 0x0f;

    if (data_type == WCT_PONG) {
        impl->last_pong_ms = timer_get_ms();
        return;
    }

    if (data_type == WCT_TXTDATA) {
        impl->callback(HAL_WS_EVENT_TEXT_RECEIVED, pkg_data, pkg_len, impl->user_ctx);
    } else if (data_type == WCT_BINDATA) {
        impl->callback(HAL_WS_EVENT_BINARY_RECEIVED, pkg_data, pkg_len, impl->user_ctx);
    } else if (data_type == WCT_DISCONN) {
        BD_LOGW(TAG, "WCT_DISCONN received");
        notify_disconnect_once(impl);
    }
}

hal_ws_handle_t hal_ws_create(const hal_ws_config_t *config)
{
    BD_LOGI(TAG, "WS IMPL: JIELI (platform/jieli)");

    if (!config || !config->url || !config->callback) {
        return NULL;
    }

    hal_ws_impl_t *impl = (hal_ws_impl_t *)zalloc(sizeof(hal_ws_impl_t));
    if (!impl) {
        return NULL;
    }

    impl->url = strdup(config->url);
    if (!impl->url) {
        free(impl);
        return NULL;
    }

    impl->callback = config->callback;
    impl->user_ctx = config->user_ctx;
    impl->recv_buf_size = config->recv_buf_size ? config->recv_buf_size : 4096;
    impl->connected = false;
    impl->running = false;

    os_mutex_create(&impl->mutex);

    return (hal_ws_handle_t)impl;
}

int hal_ws_connect(hal_ws_handle_t handle)
{
    if (!handle) {
        return -1;
    }
    hal_ws_impl_t *impl = (hal_ws_impl_t *)handle;

    if (impl->connected) {
        return 0;
    }

    g_ws_impl = impl;

    /* 注册 WebSocket 平台接口 */
    memset(&impl->ws_info, 0, sizeof(struct websocket_struct));
    impl->ws_info._init         = websockets_client_socket_init;
    impl->ws_info._exit         = websockets_client_socket_exit;
    impl->ws_info._handshack    = webcockets_client_socket_handshack;
    impl->ws_info._send         = websockets_client_socket_send;
    impl->ws_info._recv_thread  = websockets_client_socket_recv_thread;
    impl->ws_info._heart_thread = NULL;  /* 心跳由本模块统一发送 */
    impl->ws_info._recv_cb      = ws_data_callback;
    impl->ws_info._recv         = NULL;
    impl->ws_info.ip_or_url     = (u8 *)impl->url;
    impl->ws_info.recv_time_out = RECV_TIMEOUT_MS;

    /* 根据 URL 协议选择连接类型 */
    if (strncmp(impl->url, "wss://", 6) == 0) {
        impl->ws_info.websocket_mode = 1;  /* WEBSOCKETS_MODE = SSL */
    } else {
        impl->ws_info.websocket_mode = 0;  /* ws:// */
    }

    /* 校验 WebSocket 配置结构 */
    int ret = websockets_struct_check(sizeof(struct websocket_struct));
    if (ret == 0) {
        BD_LOGE(TAG, "ws struct check fail");
        return -1;
    }

    /* 连接 socket：_init 返回 0 表示失败，非 0 表示成功 */
    ret = impl->ws_info._init(&impl->ws_info);
    if (ret == 0) {
        BD_LOGE(TAG, "ws init fail");
        return -1;
    }

    ret = impl->ws_info._handshack(&impl->ws_info);
    if (ret == 0) {
        BD_LOGE(TAG, "ws handshake fail");
        impl->ws_info._exit(&impl->ws_info);
        return -1;
    }

    impl->connected = true;
    impl->running = true;
    impl->disconnect_notified = false;

    u32 now = timer_get_ms();
    impl->last_recv_ms = now;
    impl->last_ping_ms = now;
    impl->last_pong_ms = now;
    impl->ping_running = true;

    /* 启动自定义 ping 线程（禁用官方心跳） */
    char thread_name[48];
    snprintf(thread_name, sizeof(thread_name),
             "ws_ping_%d", (u16)(random32(0) & 0xFFFF));
    thread_fork(thread_name, 19, PING_THREAD_STACK, 0,
                &impl->ws_info.ping_thread_id,
                ping_thread_entry, impl);

    snprintf(thread_name, sizeof(thread_name),
             "ws_recv_%d", (u16)(random32(0) & 0xFFFF));
    thread_fork(thread_name, 18, 4096, 0,
                &impl->ws_info.recv_thread_id,
                recv_thread_entry,
                impl);

    if (impl->callback) {
        impl->callback(HAL_WS_EVENT_CONNECTED, NULL, 0, impl->user_ctx);
    }

    BD_LOGI(TAG, "WebSocket connected");
    return 0;
}

int hal_ws_send_text(hal_ws_handle_t handle, const char *text)
{
    if (!handle || !text) {
        return -1;
    }
    hal_ws_impl_t *impl = (hal_ws_impl_t *)handle;

    if (!impl->connected) {
        return -1;
    }

    os_mutex_pend(&impl->mutex, 0);
    int ret = impl->ws_info._send(&impl->ws_info, (u8 *)text, strlen(text), WCT_TXTDATA);
    os_mutex_post(&impl->mutex);

    if (ret < 0) {
        /* 发送失败视为已断连 */
        BD_LOGE(TAG, "send_text failed (ret=%d), notify disconnect", ret);
        notify_disconnect_once(impl);
        return -1;
    }
    return 0;
}

int hal_ws_send_binary(hal_ws_handle_t handle, const void *data, size_t len)
{
    if (!handle || !data || len == 0) {
        return -1;
    }
    hal_ws_impl_t *impl = (hal_ws_impl_t *)handle;

    if (!impl->connected) {
        return -1;
    }

    os_mutex_pend(&impl->mutex, 0);
    int ret = impl->ws_info._send(&impl->ws_info, (u8 *)data, len, WCT_BINDATA);
    os_mutex_post(&impl->mutex);

    if (ret < 0) {
        BD_LOGE(TAG, "send_binary failed (ret=%d), notify disconnect", ret);
        notify_disconnect_once(impl);
        return -1;
    }
    return 0;
}

int hal_ws_disconnect(hal_ws_handle_t handle)
{
    if (!handle) {
        return -1;
    }
    hal_ws_impl_t *impl = (hal_ws_impl_t *)handle;

    impl->ping_running = false;
    impl->connected = false;
    impl->disconnect_notified = true;

    /* 断开连接时停止接收回调 */
    if (g_ws_impl == impl) {
        g_ws_impl = NULL;
    }

    /* 接收线程仍在运行时，先退出 WebSocket 连接。 */
    if (impl->running) {
        impl->running = false;
        impl->ws_info._exit(&impl->ws_info);
    }

    return 0;
}

void hal_ws_destroy(hal_ws_handle_t handle)
{
    if (!handle) {
        return;
    }
    hal_ws_impl_t *impl = (hal_ws_impl_t *)handle;

    hal_ws_disconnect(handle);
    os_time_dly(30); /* 等待连接线程退出 */

    os_mutex_del(&impl->mutex, 0);

    if (impl->url) {
        free(impl->url);
    }

    if (g_ws_impl == impl) {
        g_ws_impl = NULL;
    }

    free(impl);
}

bool hal_ws_is_connected(hal_ws_handle_t handle)
{
    if (!handle) {
        return false;
    }
    return ((hal_ws_impl_t *)handle)->connected;
}

#endif /* WS_IMPL_JIELI */
