/**
 * @file    test_datacenter.cpp
 * @brief   数据中心模块单元测试
 * @details 覆盖：生命周期、5 种信号注册/链接、out 延迟回调、yk 同步控制、
 *          ao SBO 流程、param 多区定值、信号查询、类型工具、事件队列
 */
#include "myDatacenter.h"
#include "myBase.h"
#include <cstdio>
#include <cstring>
#include <cassert>

/* ===== 测试辅助 ===== */

static int g_pass = 0;
static int g_fail = 0;

#define TEST(name) \
    do \
    { \
        printf("  TEST: %s ... ", name); \
    } while (0)

#define OK() \
    do \
    { \
        printf("OK\n"); \
        g_pass++; \
    } while (0)

#define FAIL(msg) \
    do \
    { \
        printf("FAIL: %s\n", msg); \
        g_fail++; \
    } while (0)

/* ===== 回调测试变量 ===== */

static int g_out_changed = 0;
static int g_yk_changed = 0;
static int g_ao_changed = 0;
static int g_param_changed = 0;
static int g_soe_received = 0;

static void on_out_changed(const char *saddr, uint8_t data_type,
                            const void *p_new, const void *p_old)
{
    (void)saddr;
    (void)data_type;
    (void)p_new;
    (void)p_old;

    g_out_changed++;
}

static void on_yk_changed(const char *saddr, dc_ctrl_step_t step,
                           uint8_t data_type, uint8_t zone,
                           const void *p_data)
{
    (void)saddr;
    (void)step;
    (void)data_type;
    (void)zone;
    (void)p_data;

    g_yk_changed++;
}

static void on_ao_changed(const char *saddr, dc_ctrl_step_t step,
                           uint8_t data_type, uint8_t zone,
                           const void *p_data)
{
    (void)saddr;
    (void)step;
    (void)data_type;
    (void)zone;
    (void)p_data;

    g_ao_changed++;
}

static void on_param_changed(const char *saddr, dc_ctrl_step_t step,
                              uint8_t data_type, uint8_t zone,
                              const void *p_data)
{
    (void)saddr;
    (void)step;
    (void)data_type;
    (void)zone;
    (void)p_data;

    g_param_changed++;
}

static void on_soe_pop(void *p_data)
{
    (void)p_data;

    g_soe_received++;
}

/* ===== 测试用例 ===== */

/**
 * @brief 测试 1：生命周期
 */
static void test_lifecycle(void)
{
    TEST("dc_init()");
    {
        int r = dc_init(NULL);

        if (DC_OK != r)
        {
            FAIL("init failed");

            return;
        }

        OK();
    }

    TEST("dc_init() double");
    {
        int r = dc_init(NULL);

        if (DC_OK != r)
        {
            FAIL("double init should return OK");

            return;
        }

        OK();
    }

    TEST("dc_cleanup()");
    {
        dc_cleanup();

        /* re-init for following tests */
        dc_init(NULL);
        OK();
    }
}

/**
 * @brief 测试 2：out 信号注册 + 设值 + 延迟回调
 */
static void test_out_signal(void)
{
    int yc_val = 100;

    TEST("dc_signal_out()");
    {
        int r = dc_signal_out("RPC1:YC:1", "test yc", DATA_TYPE_S32,
                              &yc_val, "protocol");

        if (DC_OK != r)
        {
            FAIL("register failed");

            return;
        }

        OK();
    }

    TEST("dc_signal_out() duplicate");
    {
        int r = dc_signal_out("RPC1:YC:1", "dup", DATA_TYPE_S32,
                              &yc_val, "protocol");

        if (DC_ERR_EXISTS != r)
        {
            FAIL("duplicate should return EXISTS");

            return;
        }

        OK();
    }

    TEST("dc_signal_out_link_with_callback()");
    {
        void *p = NULL;

        g_out_changed = 0;
        int r = dc_signal_out_link_with_callback("RPC1:YC:1", &p,
                                                  on_out_changed, "web");

        if (DC_OK != r || p != &yc_val)
        {
            FAIL("link failed");

            return;
        }

        OK();
    }

    TEST("dc_set_out_signal_val() + dc_run_100ms()");
    {
        g_out_changed = 0;
        yc_val = 200;
        int r = dc_set_out_signal_val("RPC1:YC:1", &yc_val, "protocol");

        if (DC_OK != r)
        {
            FAIL("set val failed");

            return;
        }

        /* 回路阻断：protocol 自身不会被回调 */
        dc_run_100ms();

        if (g_out_changed != 1)
        {
            char msg[64];

            snprintf(msg, sizeof(msg), "cb count=%d, expected 1", g_out_changed);
            FAIL(msg);

            return;
        }

        OK();
    }
}

/**
 * @brief 测试 4：yk 信号 + 同步控制
 */
static void test_yk_signal(void)
{
    uint8_t yk_val = 0;

    TEST("dc_signal_yk()");
    {
        int r = dc_signal_yk("RPC1:YK:1", "test yk", DATA_TYPE_U8,
                             DC_CTRL_DIRECT, &yk_val, on_yk_changed, "web");

        if (DC_OK != r)
        {
            FAIL("yk register failed");

            return;
        }

        OK();
    }

    TEST("dc_signal_yk_set_status() DIRECT");
    {
        g_yk_changed = 0;
        dc_ctrl_t ctrl = {};

        ctrl.type = DC_CTRL_DIRECT;
        ctrl.data_type = DATA_TYPE_U8;

        uint8_t v = 1;
        int r = dc_signal_yk_set_status("RPC1:YK:1", DC_STEP_DIRECT,
                                         &ctrl, &v, "protocol");

        if (DC_OK != r || yk_val != 1)
        {
            FAIL("yk set failed");

            return;
        }

        if (g_yk_changed != 1)
        {
            FAIL("yk callback not triggered");

            return;
        }

        /* 回路阻断：web 自身 */ OK();
    }

    TEST("dc_signal_yk_set_status() SBO flow");
    {
        g_yk_changed = 0;
        dc_ctrl_t ctrl = {};

        ctrl.type = DC_CTRL_SBO;
        ctrl.data_type = DATA_TYPE_U8;
        ctrl.p_data = malloc(1);
        *(uint8_t *)ctrl.p_data = 2;

        uint8_t sel_val = 2;
        int r = dc_signal_yk_set_status("RPC1:YK:1", DC_STEP_SELECT,
                                         &ctrl, &sel_val, "web");

        if (DC_OK != r)
        {
            FAIL("SBO SELECT failed");

            free(ctrl.p_data);

            return;
        }

        /* DIRECT with matching value */
        r = dc_signal_yk_set_status("RPC1:YK:1", DC_STEP_DIRECT,
                                     &ctrl, &sel_val, "web");

        if (DC_OK != r)
        {
            FAIL("SBO DIRECT failed");

            free(ctrl.p_data);

            return;
        }

        free(ctrl.p_data);
        OK();
    }
}

/**
 * @brief 测试 5：ao 信号 + SBO 控制
 */
static void test_ao_signal(void)
{
    float ao_val = 0.0f;

    TEST("dc_signal_ao()");
    {
        int r = dc_signal_ao("RPC1:AO:1", "test ao", DATA_TYPE_F32,
                             DC_CTRL_SBO, &ao_val, on_ao_changed, "protocol");

        if (DC_OK != r)
        {
            FAIL("ao register failed");

            return;
        }

        OK();
    }

    TEST("dc_signal_ao_set_val() SBO");
    {
        g_ao_changed = 0;
        dc_ctrl_t ctrl = {};

        ctrl.type = DC_CTRL_SBO;
        ctrl.data_type = DATA_TYPE_F32;
        ctrl.p_data = malloc(4);
        *(float *)ctrl.p_data = 3.14f;

        float v = 3.14f;
        int r = dc_signal_ao_set_val("RPC1:AO:1", DC_STEP_SELECT,
                                      &ctrl, &v, "web");

        if (DC_OK != r)
        {
            FAIL("ao SELECT failed");

            free(ctrl.p_data);

            return;
        }

        r = dc_signal_ao_set_val("RPC1:AO:1", DC_STEP_DIRECT,
                                  &ctrl, &v, "web");

        if (DC_OK != r)
        {
            FAIL("ao DIRECT failed");

            free(ctrl.p_data);

            return;
        }

        free(ctrl.p_data);

        if (g_ao_changed < 2)
        {
            FAIL("ao callback not triggered");

            return;
        }

        /* ctrl->step should be reset to READY */
        if (ctrl.step != DC_STEP_READY)
        {
            FAIL("ctrl step not reset to READY");

            return;
        }

        OK();
    }

    TEST("dc_signal_ao_set_val_without_check()");
    {
        g_ao_changed = 0;
        float v = 2.71f;
        int r = dc_signal_ao_set_val_without_check("RPC1:AO:1", DATA_TYPE_F32,
                                                    &v, "protocol");

        if (DC_OK != r)
        {
            FAIL("ao set without check failed");

            return;
        }

        OK();
    }
}

/**
 * @brief 测试 6：param 多区定值
 */
static void test_param_signal(void)
{
    uint32_t p1 = 10;
    uint32_t p2 = 20;
    void *parr[2] = {&p1, &p2};

    TEST("dc_signal_param()");
    {
        int r = dc_signal_param("RPC1:PARAM:1", "test param", DATA_TYPE_U32,
                                DC_CTRL_SBO, parr, 2, on_param_changed, "web");

        if (DC_OK != r)
        {
            FAIL("param register failed");

            return;
        }

        OK();
    }

    TEST("dc_signal_param_set_val() zone 0");
    {
        g_param_changed = 0;
        dc_ctrl_t ctrl = {};

        ctrl.type = DC_CTRL_SBO;
        ctrl.data_type = DATA_TYPE_U32;
        ctrl.p_data = malloc(4);
        *(uint32_t *)ctrl.p_data = 55;

        uint32_t v = 55;
        int r = dc_signal_param_set_val("RPC1:PARAM:1", DC_STEP_SELECT,
                                         &ctrl, 0, &v, "web");

        if (DC_OK != r)
        {
            FAIL("param SELECT failed");

            free(ctrl.p_data);

            return;
        }

        r = dc_signal_param_set_val("RPC1:PARAM:1", DC_STEP_DIRECT,
                                     &ctrl, 0, &v, "web");

        if (DC_OK != r)
        {
            FAIL("param DIRECT failed");

            free(ctrl.p_data);

            return;
        }

        free(ctrl.p_data);

        if (p1 != 55)
        {
            FAIL("zone0 not updated");

            return;
        }

        OK();
    }
}

/**
 * @brief 测试 7：信号查询
 */
static void test_query(void)
{
    TEST("dc_get_out_signal_info()");
    {
        char desc[64] = {0};
        uint8_t dt = 0;
        void *p = NULL;
        int r = dc_get_out_signal_info("RPC1:YC:1", desc, 64, &dt, &p);

        if (DC_OK != r || dt != DATA_TYPE_S32)
        {
            FAIL("query out failed");

            return;
        }

        OK();
    }

    TEST("dc_get_signal_count()");
    {
        int n = dc_get_signal_count("out");

        if (n < 1)
        {
            FAIL("out count should be >= 1");

            return;
        }

        n = dc_get_signal_count("param");

        if (n < 1)
        {
            FAIL("param count should be >= 1");

            return;
        }

        OK();
    }

    TEST("dc_get_signal_info_by_id()");
    {
        char saddr[64] = {0};
        char desc[64] = {0};
        char dtstr[32] = {0};
        uint8_t ct = 0;
        int r = dc_get_signal_info_by_id("out", 0, saddr, 64, desc, 64,
                                          dtstr, 32, &ct, NULL, 0);

        if (DC_OK != r)
        {
            FAIL("get by id failed");

            return;
        }

        OK();
    }

    TEST("dc_get_signal_val()");
    {
        uint32_t v = 42;
        char buf[32] = {0};
        int r = dc_get_signal_val(&v, DATA_TYPE_U32, buf, 32);

        if (DC_OK != r || 0 != strcmp(buf, "42"))
        {
            FAIL("val to str failed");

            return;
        }

        OK();
    }
}

/**
 * @brief 测试 8：类型工具
 */
static void test_type_util(void)
{
    TEST("dc_get_type_name()");
    {
        const char *n = dc_get_type_name(DATA_TYPE_F32);

        if (0 != strcmp(n, "float"))
        {
            FAIL("type name mismatch");

            return;
        }

        OK();
    }

    TEST("dc_get_type_id_by_name()");
    {
        uint8_t id = dc_get_type_id_by_name("uint32_t");

        if (DATA_TYPE_U32 != id)
        {
            FAIL("type id mismatch");

            return;
        }

        OK();
    }

    TEST("dc_get_type_len()");
    {
        if (4 != dc_get_type_len(DATA_TYPE_F32))
        {
            FAIL("float len should be 4");

            return;
        }

        if (8 != dc_get_type_len(DATA_TYPE_D64))
        {
            FAIL("double len should be 8");

            return;
        }

        OK();
    }

    TEST("dc_create_data + dc_destroy_data");
    {
        void *p = dc_create_data(DATA_TYPE_S32);

        if (!p)
        {
            FAIL("create data failed");

            return;
        }

        dc_destroy_data(p, DATA_TYPE_S32);
        OK();
    }

    TEST("dc_copy_val + dc_compare_val");
    {
        int a = 10;
        int b = 0;

        dc_copy_val(&b, &a, DATA_TYPE_S32);

        if (b != 10)
        {
            FAIL("copy val failed");

            return;
        }

        if (0 != dc_compare_val(&a, &b, DATA_TYPE_S32))
        {
            FAIL("compare equal failed");

            return;
        }

        b = 20;

        if (-1 != dc_compare_val(&a, &b, DATA_TYPE_S32))
        {
            FAIL("compare unequal failed");

            return;
        }

        OK();
    }

    TEST("dc_set_val_from_str()");
    {
        float f = 0.0f;
        int r = dc_set_val_from_str(&f, DATA_TYPE_F32, "3.14");

        if (DC_OK != r || f < 3.13f || f > 3.15f)
        {
            FAIL("set from str failed");

            return;
        }

        OK();
    }

    TEST("dc_set_val_from_str() IP");
    {
        uint8_t ip[4] = {0};
        int r = dc_set_val_from_str(ip, DATA_TYPE_IP, "192.168.1.1");

        if (DC_OK != r || ip[0] != 192 || ip[1] != 168 || ip[2] != 1 || ip[3] != 1)
        {
            FAIL("IP parse failed");

            return;
        }

        OK();
    }
}

/**
 * @brief 测试 9：事件队列
 */
static void test_event_queue(void)
{
    TEST("dc_event_queue_push + register");
    {
        int r = dc_event_register_queue_pop(on_soe_pop);

        if (DC_OK != r)
        {
            FAIL("register pop failed");

            return;
        }

        g_soe_received = 0;

        int dummy = 99;
        r = dc_event_queue_push(&dummy);

        if (DC_OK != r)
        {
            FAIL("event push failed");

            return;
        }

        /* 消耗队列 */
        dc_run_100ms();

        if (g_soe_received < 1)
        {
            FAIL("event not consumed");

            return;
        }

        OK();
    }

    TEST("dc_disturb_dd_queue_push");
    {
        int dummy = 1;
        int r = dc_disturb_dd_queue_push(&dummy);

        if (DC_OK != r)
        {
            FAIL("disturb push failed");

            return;
        }

        OK();
    }

    TEST("dc_fault_queue_push");
    {
        int dummy = 1;
        int r = dc_fault_queue_push(&dummy);

        if (DC_OK != r)
        {
            FAIL("fault push failed");

            return;
        }

        OK();
    }
}

/**
 * @brief 测试 10：异步管理
 */
static void test_async_mgmt(void)
{
    TEST("dc_async_pending_count()");
    {
        int n = dc_async_pending_count();

        if (n < 0)
        {
            FAIL("pending count failed");

            return;
        }

        OK();
    }

    TEST("dc_async_set_default_timeout()");
    {
        dc_async_set_default_timeout(5000);
        OK();
    }

    TEST("dc_param_cfg_changed()");
    {
        int r = dc_param_cfg_changed();

        if (r < 0)
        {
            FAIL("cfg changed failed");

            return;
        }

        OK();
    }
}

/* ===== 入口 ===== */

int main(void)
{
    printf("=== test_datacenter ===\n\n");

    /* 初始化 */
    dc_init(NULL);

    /* 信号注册 */
    test_out_signal();
    test_yk_signal();
    test_ao_signal();
    test_param_signal();

    /* 查询 */
    test_query();

    /* 工具 */
    test_type_util();

    /* 事件队列 */
    test_event_queue();

    /* 异步管理 */
    test_async_mgmt();

    /* 清理 */
    dc_cleanup();

    /* 生命周期 */
    dc_init(NULL);
    test_lifecycle();
    dc_cleanup();

    /* 结果 */
    printf("\n=== result: %d pass, %d fail ===\n", g_pass, g_fail);

    return (g_fail > 0) ? 1 : 0;
}
