/**
 * ============================================================================
 * RCON 插件完整示例 v2.0 — 展示所有可用功能和接口
 * ============================================================================
 *
 * 本文件是一个完整的、可直接编译运行的示例插件，展示了:
 *
 *   [架构]
 *     - 必需导出函数: plugin_init / plugin_on_response
 *     - 可选导出函数: plugin_on_config_change / plugin_get_default_config / plugin_get_config_schema
 *     - DLL 入口点与资源清理
 *
 *   [数据来源]
 *     - RCON 响应解析 (聊天消息、ListPlayers等)
 *     - Logcore 日志回调 (击杀、击倒、伤害、换图)
 *
 *   [积分 API]
 *     - client_get_points    查询积分
 *     - client_add_points    增加积分
 *     - client_subtract_points 扣除积分
 *     - client_signin        签到
 *     - client_get_leaderboard 排行榜
 *
 *   [基础设施]
 *     - HTTP POST 请求封装 (WinINet)
 *     - JSON 简易解析
 *     - GBK ↔ UTF-8 编码转换
 *     - URL 编码
 *     - RCON 命令队列 (线程安全)
 *     - 云端配置热重载
 *     - 定时任务线程
 *
 * 编译 (MinGW):
 *   gcc -shared -o example_full.dll example_full.c -lws2_32 -lwininet
 *
 * 编译 (MSVC):
 *   cl /LD /Fe:example_full.dll example_full.c wininet.lib ws2_32.lib
 *
 * ============================================================================
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <wininet.h>
#include <time.h>
#include <ctype.h>

#pragma comment(lib, "wininet.lib")
#pragma comment(lib, "ws2_32.lib")

/* ============================================================================
 * 第一部分: 常量定义
 * ============================================================================ */

/* API 服务器地址 —— 所有 client_* 请求都发到这里 */
#define API_HOST            "plugin.squad.cyou"
#define API_PATH            "/api.php"
#define API_PORT            80

/* 缓冲区大小 */
#define MAX_STEAMID         64
#define MAX_NAME            128
#define MAX_MSG             512
#define MAX_CMD             512
#define MAX_TRIGGER_WORDS   20
#define MAX_TRIGGER_LEN     32
#define RESPONSE_BUF        4096
#define LOG_LINE_BUF        2048

/* RCON 命令队列 (线程安全, Logcore回调 → 主线程) */
#define RCON_QUEUE_SIZE     256

/* 玩家统计上限 */
#define MAX_PLAYERS         256

/* 插件标签 (控制台日志前缀) */
#define TAG                 "[ExampleFull]"

/* ============================================================================
 * 第二部分: Logcore 回调类型声明
 * ============================================================================ */

/**
 * Logcore 回调函数类型
 * Logcore.dll 读取日志文件后, 将每行(GBK)通过此回调通知订阅者
 */
typedef void (*LogLineCallback)(const char* line);

/* ============================================================================
 * 第三部分: 数据结构
 * ============================================================================ */

/**
 * 云端配置结构
 * 管理员在 Web 后台修改后, 主程序会调用 plugin_on_config_change() 传入 JSON
 */
typedef struct {
    int     enabled;                    /* 插件总开关                       */
    int     kill_reward;                /* 每次击杀奖励积分                 */
    int     death_penalty;              /* 每次死亡扣除积分                 */
    int     signin_reward;              /* 签到奖励                         */
    int     signin_cooldown;            /* 签到冷却 (秒)                    */
    int     auto_broadcast_interval;    /* 自动广播间隔 (秒), 0=关闭       */
    char    welcome_msg[MAX_MSG];       /* 欢迎消息                         */
    char    trigger_score[MAX_TRIGGER_WORDS][MAX_TRIGGER_LEN];  /* 查分触发词 */
    int     trigger_score_count;
    char    trigger_signin[MAX_TRIGGER_WORDS][MAX_TRIGGER_LEN]; /* 签到触发词 */
    int     trigger_signin_count;
    char    trigger_top[MAX_TRIGGER_WORDS][MAX_TRIGGER_LEN];    /* 排行触发词 */
    int     trigger_top_count;
} PluginConfig;

/**
 * 玩家本局统计 (内存中, 每局重置)
 */
typedef struct {
    char    steamid[MAX_STEAMID];
    char    name[MAX_NAME];
    int     kills;
    int     deaths;
    int     knocks;
} PlayerStats;

/* ============================================================================
 * 第四部分: 全局变量
 * ============================================================================ */

/* 主程序提供的 RCON 命令发送函数 */
static void (*g_send_command)(const char *cmd) = NULL;

/* 主程序注入的 license_key (用于 client_* API 认证) */
static char g_license_key[64] = "";

/* Logcore 回调注册函数指针 */
static void (*g_register_log_callback)(LogLineCallback cb) = NULL;

/* 云端配置 (受锁保护) */
static PluginConfig g_config;
static CRITICAL_SECTION g_config_lock;

/* 玩家统计 (受锁保护) */
static PlayerStats g_players[MAX_PLAYERS];
static int g_player_count = 0;
static CRITICAL_SECTION g_players_lock;

/* RCON 命令队列 (Logcore线程 → 主线程) */
static char g_rcon_queue[RCON_QUEUE_SIZE][MAX_CMD];
static int g_rq_head = 0;
static int g_rq_tail = 0;
static CRITICAL_SECTION g_rcon_lock;

/* 定时广播线程 */
static HANDLE g_timer_thread = NULL;
static volatile int g_timer_running = 0;

/* 初始化标记 */
static volatile int g_initialized = 0;

/* ============================================================================
 * 第五部分: 默认配置 (JSON)
 *
 * 当插件首次被激活且 Redis 中没有配置时,
 * 主程序调用 plugin_get_default_config() 获取此内容并写入 Redis。
 * 管理员随后可在 Web 后台编辑。
 *
 * 注意: JSON 中的中文使用 \uXXXX 转义, 主程序会自动转为 GBK 传入。
 * ============================================================================ */

static const char* DEFAULT_CONFIG =
"{\n"
"    \"enabled\": true,\n"
"    \"kill_reward\": 10,\n"
"    \"death_penalty\": 5,\n"
"    \"signin_reward\": 20,\n"
"    \"signin_cooldown\": 86400,\n"
"    \"auto_broadcast_interval\": 0,\n"
"    \"welcome_msg\": \"\\u6b22\\u8fce\\u4f7f\\u7528\\u793a\\u4f8b\\u63d2\\u4ef6\",\n"
"    \"trigger_score\": [\"\\u67e5\\u5206\", \"jf\", \"score\"],\n"
"    \"trigger_signin\": [\"\\u7b7e\\u5230\", \"qd\"],\n"
"    \"trigger_top\": [\"\\u6392\\u884c\", \"top\"]\n"
"}";

/* ============================================================================
 * 第六部分: 工具函数 —— 日志输出
 * ============================================================================ */

/**
 * 控制台日志输出 (GBK)
 */
static void plugin_log(const char *msg) {
    printf("%s %s\n", TAG, msg);
    fflush(stdout);
}

/* ============================================================================
 * 第七部分: 工具函数 —— 编码转换
 *
 * 编码规则:
 *   - RCON 响应 / Logcore 回调 / send_command = GBK
 *   - API 的 reason / player_name 参数       = UTF-8 + URL编码
 *   - 日志文件原始内容                       = UTF-8 (Logcore已转GBK)
 * ============================================================================ */

/**
 * GBK → UTF-8
 * 调用 API 时, 中文参数必须先转 UTF-8
 */
static int gbk_to_utf8(const char *gbk, char *utf8, size_t utf8_size) {
    if (!gbk || !utf8 || utf8_size == 0) return 0;

    int wlen = MultiByteToWideChar(CP_ACP, 0, gbk, -1, NULL, 0);
    if (wlen <= 0) {
        strncpy(utf8, gbk, utf8_size - 1);
        utf8[utf8_size - 1] = '\0';
        return 0;
    }

    wchar_t *wstr = (wchar_t *)malloc(wlen * sizeof(wchar_t));
    if (!wstr) {
        strncpy(utf8, gbk, utf8_size - 1);
        utf8[utf8_size - 1] = '\0';
        return 0;
    }

    MultiByteToWideChar(CP_ACP, 0, gbk, -1, wstr, wlen);

    int result = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, utf8, (int)utf8_size, NULL, NULL);
    free(wstr);

    if (result <= 0) {
        strncpy(utf8, gbk, utf8_size - 1);
        utf8[utf8_size - 1] = '\0';
        return 0;
    }
    return 1;
}

/**
 * UTF-8 → GBK
 * 日志中提取的玩家名(UTF-8)要转回 GBK 才能用于 RCON 命令
 */
static int utf8_to_gbk(const char *utf8, char *gbk, size_t gbk_size) {
    if (!utf8 || !gbk || gbk_size == 0) return 0;

    int wlen = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
    if (wlen <= 0) {
        strncpy(gbk, utf8, gbk_size - 1);
        gbk[gbk_size - 1] = '\0';
        return 0;
    }

    wchar_t *wstr = (wchar_t *)malloc(wlen * sizeof(wchar_t));
    if (!wstr) {
        strncpy(gbk, utf8, gbk_size - 1);
        gbk[gbk_size - 1] = '\0';
        return 0;
    }

    MultiByteToWideChar(CP_UTF8, 0, utf8, -1, wstr, wlen);

    int result = WideCharToMultiByte(CP_ACP, 0, wstr, -1, gbk, (int)gbk_size, NULL, NULL);
    free(wstr);

    if (result <= 0) {
        strncpy(gbk, utf8, gbk_size - 1);
        gbk[gbk_size - 1] = '\0';
        return 0;
    }
    return 1;
}

/**
 * URL 编码
 * API 参数中的中文必须先转 UTF-8 再 URL 编码, 否则服务端接收乱码
 */
static void url_encode(const char *src, char *dest, size_t dest_size) {
    if (!src || !dest || dest_size == 0) return;
    static const char *hex = "0123456789ABCDEF";
    size_t pos = 0;

    while (*src && pos < dest_size - 4) {
        unsigned char c = (unsigned char)*src;
        if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
            dest[pos++] = *src;
        } else if (*src == ' ') {
            dest[pos++] = '+';
        } else {
            dest[pos++] = '%';
            dest[pos++] = hex[c >> 4];
            dest[pos++] = hex[c & 0x0F];
        }
        src++;
    }
    dest[pos] = '\0';
}

/* ============================================================================
 * 第八部分: 工具函数 —— JSON 简易解析
 *
 * 仅适用于扁平 JSON, 不支持嵌套。复杂场景推荐 cJSON 库。
 * ============================================================================ */

static int json_get_string(const char *json, const char *key, char *out, size_t out_size) {
    if (!json || !key || !out || out_size == 0) return 0;
    char search[256];
    snprintf(search, sizeof(search), "\"%s\"", key);
    const char *p = strstr(json, search);
    if (!p) return 0;
    p += strlen(search);
    while (*p && (*p == ' ' || *p == ':' || *p == '\t' || *p == '\n' || *p == '\r')) p++;
    if (*p != '"') return 0;
    p++;
    size_t i = 0;
    while (*p && *p != '"' && i < out_size - 1) {
        if (*p == '\\' && *(p + 1)) {
            p++;
            switch (*p) {
                case 'n':  out[i++] = '\n'; break;
                case 't':  out[i++] = '\t'; break;
                case 'r':  out[i++] = '\r'; break;
                case '"':  out[i++] = '"';  break;
                case '\\': out[i++] = '\\'; break;
                default:   out[i++] = *p;   break;
            }
        } else {
            out[i++] = *p;
        }
        p++;
    }
    out[i] = '\0';
    return 1;
}

static int json_get_int(const char *json, const char *key, int *out) {
    if (!json || !key || !out) return 0;
    char search[256];
    snprintf(search, sizeof(search), "\"%s\"", key);
    const char *p = strstr(json, search);
    if (!p) return 0;
    p += strlen(search);
    while (*p && (*p == ' ' || *p == ':' || *p == '\t' || *p == '\n' || *p == '\r')) p++;
    if (!isdigit((unsigned char)*p) && *p != '-') return 0;
    *out = atoi(p);
    return 1;
}

static int json_get_bool(const char *json, const char *key, int *out) {
    if (!json || !key || !out) return 0;
    char search[256];
    snprintf(search, sizeof(search), "\"%s\"", key);
    const char *p = strstr(json, search);
    if (!p) return 0;
    p += strlen(search);
    while (*p && (*p == ' ' || *p == ':' || *p == '\t' || *p == '\n' || *p == '\r')) p++;
    if (strncmp(p, "true", 4) == 0)  { *out = 1; return 1; }
    if (strncmp(p, "false", 5) == 0) { *out = 0; return 1; }
    return 0;
}

/**
 * 检查 API 响应是否成功
 */
static int json_is_success(const char *json) {
    const char *p = strstr(json, "\"success\"");
    if (!p) return 0;
    p = strstr(p, ":");
    if (!p) return 0;
    while (*p && (*p == ':' || *p == ' ' || *p == '\t')) p++;
    return strncmp(p, "true", 4) == 0;
}

/**
 * 解析 JSON 字符串数组到二维 char 数组
 * 返回元素数量
 */
static int json_get_string_array(const char *json, const char *key,
                                  char arr[][MAX_TRIGGER_LEN], int max_count) {
    char search[256];
    snprintf(search, sizeof(search), "\"%s\"", key);
    const char *p = strstr(json, search);
    if (!p) return 0;
    p = strchr(p, '[');
    if (!p) return 0;
    p++;
    int count = 0;
    while (*p && count < max_count) {
        while (*p && (*p == ' ' || *p == ',' || *p == '\t' || *p == '\n' || *p == '\r')) p++;
        if (*p == ']') break;
        if (*p == '"') {
            p++;
            size_t i = 0;
            while (*p && *p != '"' && i < MAX_TRIGGER_LEN - 1) {
                if (*p == '\\' && *(p + 1)) { p++; }
                arr[count][i++] = *p++;
            }
            arr[count][i] = '\0';
            if (*p == '"') p++;
            count++;
        } else {
            p++;
        }
    }
    return count;
}

/* ============================================================================
 * 第九部分: RCON 命令队列 (线程安全)
 *
 * 重要: Logcore 回调在 Logcore 的读取线程中执行。
 *        不能直接调用 g_send_command, 必须通过队列中转。
 *
 * 流程:
 *   Logcore线程 → enqueue_rcon()  入队
 *   主线程     → flush_rcon_queue() 出队并发送
 *
 * flush_rcon_queue() 在 plugin_on_response() 开头调用,
 * 每次收到 RCON 响应时自动清空队列。
 * ============================================================================ */

static void enqueue_rcon(const char *cmd) {
    EnterCriticalSection(&g_rcon_lock);
    int next = (g_rq_tail + 1) % RCON_QUEUE_SIZE;
    if (next != g_rq_head) {
        strncpy(g_rcon_queue[g_rq_tail], cmd, MAX_CMD - 1);
        g_rcon_queue[g_rq_tail][MAX_CMD - 1] = '\0';
        g_rq_tail = next;
    }
    LeaveCriticalSection(&g_rcon_lock);
}

static void flush_rcon_queue(void) {
    char cmd[MAX_CMD];
    EnterCriticalSection(&g_rcon_lock);
    while (g_rq_head != g_rq_tail) {
        strncpy(cmd, g_rcon_queue[g_rq_head], MAX_CMD - 1);
        cmd[MAX_CMD - 1] = '\0';
        g_rq_head = (g_rq_head + 1) % RCON_QUEUE_SIZE;
        LeaveCriticalSection(&g_rcon_lock);
        if (g_send_command) g_send_command(cmd);
        EnterCriticalSection(&g_rcon_lock);
    }
    LeaveCriticalSection(&g_rcon_lock);
}

/**
 * 便捷函数: 向指定玩家发送提示 (线程安全, 可在 Logcore 回调中使用)
 */
static void rcon_warn(const char *steamid, const char *msg) {
    char buf[MAX_CMD];
    snprintf(buf, sizeof(buf), "AdminWarn %s %s", steamid, msg);
    enqueue_rcon(buf);
}

/**
 * 便捷函数: 全服广播 (线程安全)
 */
static void rcon_broadcast(const char *msg) {
    char buf[MAX_CMD];
    snprintf(buf, sizeof(buf), "AdminBroadcast %s", msg);
    enqueue_rcon(buf);
}

/* ============================================================================
 * 第十部分: HTTP 请求封装 (WinINet)
 *
 * 所有 client_* API 调用的底层传输。
 * POST 到 http://plugin.squad.cyou/api.php?action=<action>
 * Body: action=<action>&license_key=<key>&<extra_params>
 * ============================================================================ */

static int http_post(const char *action, const char *post_data,
                     char *response, size_t response_size) {
    if (!action || !response || response_size == 0) return 0;

    HINTERNET hInternet = NULL, hConnect = NULL, hRequest = NULL;
    int result = 0;

    hInternet = InternetOpenA("ExamplePlugin/2.0",
                              INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
    if (!hInternet) return 0;

    hConnect = InternetConnectA(hInternet, API_HOST, API_PORT,
                                NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0);
    if (!hConnect) {
        InternetCloseHandle(hInternet);
        return 0;
    }

    char url_path[512];
    snprintf(url_path, sizeof(url_path), "%s?action=%s", API_PATH, action);

    hRequest = HttpOpenRequestA(hConnect, "POST", url_path, NULL, NULL, NULL,
                                INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE, 0);
    if (!hRequest) {
        InternetCloseHandle(hConnect);
        InternetCloseHandle(hInternet);
        return 0;
    }

    const char *headers = "Content-Type: application/x-www-form-urlencoded\r\n";

    /* 构造完整 POST body: action=xxx&license_key=xxx&extra... */
    char full_body[4096] = "";
    if (post_data && strlen(post_data) > 0) {
        snprintf(full_body, sizeof(full_body), "action=%s&%s", action, post_data);
    } else {
        snprintf(full_body, sizeof(full_body), "action=%s", action);
    }

    if (!HttpSendRequestA(hRequest, headers, (DWORD)strlen(headers),
                          full_body, (DWORD)strlen(full_body))) {
        InternetCloseHandle(hRequest);
        InternetCloseHandle(hConnect);
        InternetCloseHandle(hInternet);
        return 0;
    }

    /* 读取响应 (使用 memcpy 替代 strcat, O(n) 而非 O(n²)) */
    DWORD bytes_read = 0;
    size_t total = 0;
    char buffer[4096];
    response[0] = '\0';

    while (InternetReadFile(hRequest, buffer, sizeof(buffer) - 1, &bytes_read)
           && bytes_read > 0) {
        if (total + bytes_read < response_size - 1) {
            memcpy(response + total, buffer, bytes_read);
            total += bytes_read;
            response[total] = '\0';
        } else {
            break;
        }
    }

    result = total > 0;
    InternetCloseHandle(hRequest);
    InternetCloseHandle(hConnect);
    InternetCloseHandle(hInternet);
    return result;
}

/* ============================================================================
 * 第十一部分: 积分 API 封装 (client_*)
 *
 * 所有接口使用 license_key 认证, 由主程序通过 plugin_on_response
 * 发送 "LICENSE_KEY:xxxx" 消息注入, 无需额外申请。
 * ============================================================================ */

/**
 * client_get_points — 查询玩家积分
 * @return 积分值, 失败返回 -1
 */
static int api_get_points(const char *steamid) {
    char post_data[256];
    snprintf(post_data, sizeof(post_data),
             "license_key=%s&steamid=%s", g_license_key, steamid);

    char response[RESPONSE_BUF];
    if (!http_post("client_get_points", post_data, response, sizeof(response)))
        return -1;

    int points = 0;
    if (json_get_int(response, "points", &points))
        return points;
    return -1;
}

/**
 * client_add_points — 增加积分
 * @param reason_gbk 原因(GBK编码), 函数内部自动转 UTF-8 + URL编码
 * @return 1=成功, 0=失败
 */
static int api_add_points(const char *steamid, int amount,
                          const char *reason_gbk, const char *player_name_gbk) {
    /* 编码转换: reason GBK → UTF-8 → URL编码 */
    char reason_utf8[256] = "", reason_encoded[512] = "";
    if (reason_gbk && reason_gbk[0]) {
        gbk_to_utf8(reason_gbk, reason_utf8, sizeof(reason_utf8));
        url_encode(reason_utf8, reason_encoded, sizeof(reason_encoded));
    }

    /* 编码转换: player_name GBK → UTF-8 → URL编码 */
    char name_utf8[256] = "", name_encoded[512] = "";
    if (player_name_gbk && player_name_gbk[0]) {
        gbk_to_utf8(player_name_gbk, name_utf8, sizeof(name_utf8));
        url_encode(name_utf8, name_encoded, sizeof(name_encoded));
    }

    char post_data[2048];
    snprintf(post_data, sizeof(post_data),
             "license_key=%s&steamid=%s&amount=%d&reason=%s&player_name=%s",
             g_license_key, steamid, amount, reason_encoded, name_encoded);

    char response[RESPONSE_BUF];
    if (!http_post("client_add_points", post_data, response, sizeof(response)))
        return 0;

    return json_is_success(response);
}

/**
 * client_subtract_points — 扣除积分
 * @return 1=成功, 0=失败(含积分不足)
 */
static int api_subtract_points(const char *steamid, int amount,
                               const char *reason_gbk) {
    char reason_utf8[256] = "", reason_encoded[512] = "";
    if (reason_gbk && reason_gbk[0]) {
        gbk_to_utf8(reason_gbk, reason_utf8, sizeof(reason_utf8));
        url_encode(reason_utf8, reason_encoded, sizeof(reason_encoded));
    }

    char post_data[1024];
    snprintf(post_data, sizeof(post_data),
             "license_key=%s&steamid=%s&amount=%d&reason=%s",
             g_license_key, steamid, amount, reason_encoded);

    char response[RESPONSE_BUF];
    if (!http_post("client_subtract_points", post_data, response, sizeof(response)))
        return 0;

    return json_is_success(response);
}

/**
 * client_signin — 玩家签到 (服务端自动处理冷却)
 * @return 1=签到成功, 0=冷却中, -1=请求失败
 */
static int api_signin(const char *steamid, const char *player_name_gbk,
                      int reward, int cooldown) {
    char name_utf8[256] = "", name_encoded[512] = "";
    if (player_name_gbk && player_name_gbk[0]) {
        gbk_to_utf8(player_name_gbk, name_utf8, sizeof(name_utf8));
        url_encode(name_utf8, name_encoded, sizeof(name_encoded));
    }

    char post_data[1024];
    snprintf(post_data, sizeof(post_data),
             "license_key=%s&steamid=%s&player_name=%s&reward=%d&cooldown=%d",
             g_license_key, steamid, name_encoded, reward, cooldown);

    char response[RESPONSE_BUF];
    if (!http_post("client_signin", post_data, response, sizeof(response)))
        return -1;

    /* 检查结果 */
    char result[32] = "";
    json_get_string(response, "result", result, sizeof(result));
    if (strcmp(result, "success") == 0) return 1;
    if (strcmp(result, "cooldown") == 0) return 0;
    return -1;
}

/**
 * client_get_leaderboard — 获取排行榜
 * @param resp 输出: 完整 JSON 响应
 * @return 1=成功, 0=失败
 */
static int api_get_leaderboard(char *resp, size_t resp_size,
                               int page, int page_size) {
    char post_data[256];
    snprintf(post_data, sizeof(post_data),
             "license_key=%s&page=%d&page_size=%d",
             g_license_key, page, page_size);

    return http_post("client_get_leaderboard", post_data, resp, resp_size);
}

/* ============================================================================
 * 第十二部分: 玩家统计管理 (内存, 每局重置)
 * ============================================================================ */

static PlayerStats* get_player(const char *steamid) {
    for (int i = 0; i < g_player_count; i++) {
        if (strcmp(g_players[i].steamid, steamid) == 0)
            return &g_players[i];
    }
    if (g_player_count >= MAX_PLAYERS) return NULL;
    PlayerStats *p = &g_players[g_player_count++];
    memset(p, 0, sizeof(*p));
    strncpy(p->steamid, steamid, MAX_STEAMID - 1);
    return p;
}

static void reset_all_stats(void) {
    EnterCriticalSection(&g_players_lock);
    g_player_count = 0;
    memset(g_players, 0, sizeof(g_players));
    LeaveCriticalSection(&g_players_lock);
    plugin_log("Stats reset (new game)");
}

/* ============================================================================
 * 第十三部分: 配置解析
 * ============================================================================ */

static void parse_config(const char *json, PluginConfig *cfg) {
    /* 默认值 */
    cfg->enabled = 1;
    cfg->kill_reward = 10;
    cfg->death_penalty = 5;
    cfg->signin_reward = 20;
    cfg->signin_cooldown = 86400;
    cfg->auto_broadcast_interval = 0;
    strcpy(cfg->welcome_msg, "");
    cfg->trigger_score_count = 0;
    cfg->trigger_signin_count = 0;
    cfg->trigger_top_count = 0;

    if (!json || !json[0]) return;

    /* 解析各字段 */
    json_get_bool(json, "enabled", &cfg->enabled);
    json_get_int(json, "kill_reward", &cfg->kill_reward);
    json_get_int(json, "death_penalty", &cfg->death_penalty);
    json_get_int(json, "signin_reward", &cfg->signin_reward);
    json_get_int(json, "signin_cooldown", &cfg->signin_cooldown);
    json_get_int(json, "auto_broadcast_interval", &cfg->auto_broadcast_interval);
    json_get_string(json, "welcome_msg", cfg->welcome_msg, sizeof(cfg->welcome_msg));

    /* 解析触发词数组 */
    int n;
    n = json_get_string_array(json, "trigger_score", cfg->trigger_score, MAX_TRIGGER_WORDS);
    if (n > 0) cfg->trigger_score_count = n;
    n = json_get_string_array(json, "trigger_signin", cfg->trigger_signin, MAX_TRIGGER_WORDS);
    if (n > 0) cfg->trigger_signin_count = n;
    n = json_get_string_array(json, "trigger_top", cfg->trigger_top, MAX_TRIGGER_WORDS);
    if (n > 0) cfg->trigger_top_count = n;

    char log_msg[256];
    snprintf(log_msg, sizeof(log_msg),
             "Config: enabled=%d kill=%d death=%d signin=%d/%ds bcast=%ds triggers=%d/%d/%d",
             cfg->enabled, cfg->kill_reward, cfg->death_penalty,
             cfg->signin_reward, cfg->signin_cooldown, cfg->auto_broadcast_interval,
             cfg->trigger_score_count, cfg->trigger_signin_count, cfg->trigger_top_count);
    plugin_log(log_msg);
}

/* ============================================================================
 * 第十四部分: 触发词匹配
 *
 * 使用精确前缀匹配而非全文 strstr, 避免玩家名含关键字时误触发。
 * 例如: 玩家发"查分" → 匹配; 玩家名含"查分"但消息是别的 → 不匹配
 * ============================================================================ */

static int match_trigger(const char *cmd, char triggers[][MAX_TRIGGER_LEN], int count) {
    for (int i = 0; i < count; i++) {
        size_t len = strlen(triggers[i]);
        if (len == 0) continue;
        if (strncmp(cmd, triggers[i], len) == 0) {
            char next = cmd[len];
            if (next == '\0' || next == ' ' || next == '\t')
                return 1;
        }
    }
    return 0;
}

/* ============================================================================
 * 第十五部分: Logcore 日志行处理回调
 *
 * 此函数在 Logcore 的读取线程中被调用。
 *
 * 注意:
 *   - lineGBK 是 GBK 编码 (Logcore 已从 UTF-8 转换)
 *   - 不要直接调用 g_send_command, 用 enqueue_rcon() / rcon_warn() / rcon_broadcast()
 *   - 尽量避免耗时操作 (HTTP请求等), 或考虑异步处理
 * ============================================================================ */

static void on_log_line(const char *lineGBK) {
    if (!lineGBK || !lineGBK[0]) return;

    /* 获取配置副本 (线程安全) */
    PluginConfig cfg;
    EnterCriticalSection(&g_config_lock);
    memcpy(&cfg, &g_config, sizeof(PluginConfig));
    LeaveCriticalSection(&g_config_lock);

    if (!cfg.enabled) return;

    /* ===== 检测新对局 ===== */
    if (strstr(lineGBK, "LogSquad: StartNewGame")) {
        reset_all_stats();
        return;
    }

    /* ===== 检测击杀事件: [DedicatedServer]Die() ===== */
    if (strstr(lineGBK, "[DedicatedServer]Die():")) {
        /* 转 UTF-8 以正确处理中文玩家名 */
        char lineUtf8[LOG_LINE_BUF];
        gbk_to_utf8(lineGBK, lineUtf8, sizeof(lineUtf8));

        /* 提取被击杀者名字: "Player:" → " KillingDamage=" 之间 */
        char victim_utf8[MAX_NAME] = "";
        const char *p = strstr(lineUtf8, "Player:");
        if (p) {
            p += 7; /* strlen("Player:") */
            if (*p == '=') p++;
            int i = 0;
            while (*p && i < MAX_NAME - 1) {
                if (strncmp(p, " KillingDamage=", 15) == 0) break;
                victim_utf8[i++] = *p++;
            }
            victim_utf8[i] = '\0';
            /* 去尾部空格 */
            while (i > 0 && victim_utf8[i - 1] == ' ')
                victim_utf8[--i] = '\0';
        }

        /* 提取击杀者 SteamID: " steam: " 后的数字串 */
        char killer_steam[MAX_STEAMID] = "";
        p = strstr(lineUtf8, " steam: ");
        if (p) {
            p += 8; /* strlen(" steam: ") */
            int i = 0;
            while (p[i] && p[i] != ' ' && p[i] != '|' && p[i] != '\n' && i < MAX_STEAMID - 1) {
                killer_steam[i] = p[i];
                i++;
            }
            killer_steam[i] = '\0';
        }

        if (!victim_utf8[0] || !killer_steam[0]) return;

        /* 更新本局统计 */
        EnterCriticalSection(&g_players_lock);
        PlayerStats *ks = get_player(killer_steam);
        if (ks) ks->kills++;
        LeaveCriticalSection(&g_players_lock);

        /* 给击杀者加积分 (注意: 这里有 HTTP 请求, 会阻塞 Logcore 线程) */
        /* 生产环境建议放到单独的工作线程中异步处理 */
        if (cfg.kill_reward > 0) {
            api_add_points(killer_steam, cfg.kill_reward, "\xBB\xF7\xC9\xB1\xBD\xB1\xC0\xF8", NULL);
            /* "\xBB\xF7\xC9\xB1\xBD\xB1\xC0\xF8" = "击杀奖励" GBK */
        }

        /* 发送击杀通知 */
        char victim_gbk[MAX_NAME];
        utf8_to_gbk(victim_utf8, victim_gbk, sizeof(victim_gbk));

        char msg[MAX_MSG];
        snprintf(msg, sizeof(msg), "\xC4\xE3\xBB\xF7\xC9\xB1\xC1\xCB %s (+%d)",
                 victim_gbk, cfg.kill_reward);
        /* "\xC4\xE3\xBB\xF7\xC9\xB1\xC1\xCB " = "你击杀了 " GBK */
        rcon_warn(killer_steam, msg);

        return;
    }

    /* ===== 检测击倒事件: [DedicatedServer]Wound() ===== */
    if (strstr(lineGBK, "[DedicatedServer]Wound():")) {
        /* 与击杀类似, 提取 victim / attacker, 更新 knocks 统计 */
        /* 此处省略完整实现, 逻辑同上 */
        return;
    }

    /* ===== 检测伤害事件: ActualDamage= ===== */
    if (strstr(lineGBK, "ActualDamage=") && strstr(lineGBK, "LogSquad: Player:")) {
        /* 提取被伤害者名字、伤害值、攻击者 SteamID */
        /* 此处省略完整实现 */
        return;
    }
}

/* ============================================================================
 * 第十六部分: 聊天命令处理
 *
 * RCON 响应中的聊天消息格式:
 *   [ChatAll] [SteamID:12345 steam: 76561198xxxxxxx] 玩家名 : 消息内容
 *
 * 解析流程:
 *   1. 找 " steam: " 提取 SteamID
 *   2. 找 "]" 和 " : " 提取玩家名
 *   3. " : " 之后为消息内容
 *   4. 用精确前缀匹配检查是否是命令
 * ============================================================================ */

static void handle_cmd_score(const char *steamid) {
    int pts = api_get_points(steamid);
    char msg[256];
    if (pts >= 0) {
        snprintf(msg, sizeof(msg), "\xB5\xB1\xC7\xB0\xBB\xFD\xB7\xD6: %d", pts);
        /* "\xB5\xB1\xC7\xB0\xBB\xFD\xB7\xD6: " = "当前积分: " GBK */
    } else {
        snprintf(msg, sizeof(msg), "\xB2\xE9\xD1\xAF\xCA\xA7\xB0\xDC");
        /* "查询失败" GBK */
    }
    rcon_warn(steamid, msg);
    flush_rcon_queue();
}

static void handle_cmd_signin(const char *steamid, const char *player_name,
                              const PluginConfig *cfg) {
    int ret = api_signin(steamid, player_name, cfg->signin_reward, cfg->signin_cooldown);
    char msg[256];
    if (ret == 1) {
        snprintf(msg, sizeof(msg), "\xC7\xA9\xB5\xBD\xB3\xC9\xB9\xA6 +%d \xBB\xFD\xB7\xD6!",
                 cfg->signin_reward);
        /* "签到成功 +%d 积分!" GBK */
    } else if (ret == 0) {
        snprintf(msg, sizeof(msg),
                 "\xBD\xF1\xCC\xEC\xD2\xD1\xC7\xA9\xB9\xFD\xB5\xBD\xC1\xCB");
        /* "今天已签过到了" GBK */
    } else {
        snprintf(msg, sizeof(msg), "\xC7\xA9\xB5\xBD\xCA\xA7\xB0\xDC");
        /* "签到失败" GBK */
    }
    rcon_warn(steamid, msg);
    flush_rcon_queue();
}

static void handle_cmd_top(const PluginConfig *cfg) {
    char resp[4096] = "";
    if (!api_get_leaderboard(resp, sizeof(resp), 1, 5)) {
        rcon_broadcast("\xC5\xC5\xD0\xD0\xB0\xF1\xBC\xD3\xD4\xD8\xCA\xA7\xB0\xDC");
        /* "排行榜加载失败" GBK */
        flush_rcon_queue();
        return;
    }

    /* 简单解析排行榜 JSON 并广播 */
    char bc[1024] = "AdminBroadcast [\xBB\xFD\xB7\xD6\xC5\xC5\xD0\xD0\xB0\xF1]\n";
    /* "[积分排行榜]" GBK */
    const char *p = strstr(resp, "\"leaderboard\":[");
    if (!p) {
        rcon_broadcast("\xD4\xDD\xCE\xDE\xC5\xC5\xD0\xD0\xCA\xFD\xBE\xDD");
        /* "暂无排行数据" GBK */
        flush_rcon_queue();
        return;
    }
    p += 15;

    int count = 0;
    while (*p && count < 5) {
        char name[64] = "?";
        int rank = 0, pts = 0;
        char *rp = strstr(p, "\"rank\":");
        if (rp) rank = atoi(rp + 7);
        char *np = strstr(p, "\"name\":\"");
        if (np) {
            np += 8;
            int i = 0;
            while (*np && *np != '"' && i < 63) name[i++] = *np++;
            name[i] = '\0';
        }
        char *pp = strstr(p, "\"points\":");
        if (pp) pts = atoi(pp + 9);

        char line[128];
        snprintf(line, sizeof(line), "#%d %s — %d\n", rank, name, pts);
        strncat(bc, line, sizeof(bc) - strlen(bc) - 1);

        count++;
        p = strchr(p, '}');
        if (p) p++;
        p = strchr(p, '{');
        if (!p) break;
    }

    if (g_send_command) g_send_command(bc);
}

static void handle_cmd_kd(const char *steamid) {
    EnterCriticalSection(&g_players_lock);
    PlayerStats *ps = get_player(steamid);
    int kills = ps ? ps->kills : 0;
    int deaths = ps ? ps->deaths : 0;
    LeaveCriticalSection(&g_players_lock);

    float kd = deaths > 0 ? (float)kills / deaths : (float)kills;
    char msg[256];
    snprintf(msg, sizeof(msg),
             "\xB1\xBE\xBE\xD6: \xBB\xF7\xC9\xB1%d \xCB\xC0\xCD\xF6%d K/D %.2f",
             kills, deaths, kd);
    /* "本局: 击杀%d 死亡%d K/D %.2f" GBK */
    rcon_warn(steamid, msg);
    flush_rcon_queue();
}

/* ============================================================================
 * 第十七部分: 定时任务线程 (可选)
 *
 * 如果配置了 auto_broadcast_interval > 0, 会定时发送广播。
 * 也可在此线程中做定时查询 ListPlayers 等操作。
 * ============================================================================ */

static DWORD WINAPI timer_thread_func(LPVOID param) {
    (void)param;
    plugin_log("Timer thread started");

    while (g_timer_running) {
        /* 获取配置副本 */
        PluginConfig cfg;
        EnterCriticalSection(&g_config_lock);
        memcpy(&cfg, &g_config, sizeof(PluginConfig));
        LeaveCriticalSection(&g_config_lock);

        if (cfg.enabled && cfg.auto_broadcast_interval > 0 && cfg.welcome_msg[0]) {
            rcon_broadcast(cfg.welcome_msg);
            /* 注意: 这里入队后需要等 flush_rcon_queue 在 on_response 中执行 */
        }

        /* 按配置间隔休眠, 每秒检查一次是否需要退出 */
        int wait = cfg.auto_broadcast_interval > 0 ? cfg.auto_broadcast_interval : 60;
        for (int i = 0; i < wait && g_timer_running; i++) {
            Sleep(1000);
        }
    }

    plugin_log("Timer thread stopped");
    return 0;
}

/* ============================================================================
 * 第十八部分: 插件导出函数 —— plugin_init (必需)
 *
 * 主程序加载 DLL 后第一个调用的函数。
 * 职责:
 *   1. 保存 send_command 回调
 *   2. 初始化临界区
 *   3. 加载默认配置
 *   4. 注册 Logcore 回调
 *   5. 启动定时线程 (可选)
 * ============================================================================ */

__declspec(dllexport)
void plugin_init(void (*send_command)(const char *cmd)) {
    g_send_command = send_command;

    plugin_log("Initializing v2.0...");

    /* 初始化临界区 (仅首次) */
    if (!g_initialized) {
        InitializeCriticalSection(&g_config_lock);
        InitializeCriticalSection(&g_players_lock);
        InitializeCriticalSection(&g_rcon_lock);
        memset(g_players, 0, sizeof(g_players));
        g_player_count = 0;
        g_initialized = 1;
    }

    /* 加载默认配置 */
    EnterCriticalSection(&g_config_lock);
    parse_config(DEFAULT_CONFIG, &g_config);
    LeaveCriticalSection(&g_config_lock);

    /* ===== 注册 Logcore 日志回调 ===== */
    HMODULE hLogcore = GetModuleHandleA("Logcore.dll");
    if (hLogcore) {
        g_register_log_callback = (void (*)(LogLineCallback))
            GetProcAddress(hLogcore, "plugin_register_log_callback");
        if (g_register_log_callback) {
            g_register_log_callback(on_log_line);
            plugin_log("Logcore callback registered");
        } else {
            plugin_log("WARNING: plugin_register_log_callback not found");
        }
    } else {
        plugin_log("WARNING: Logcore.dll not loaded — kill/damage detection disabled");
    }

    /* 启动定时线程 */
    if (!g_timer_thread) {
        g_timer_running = 1;
        g_timer_thread = CreateThread(NULL, 0, timer_thread_func, NULL, 0, NULL);
        if (g_timer_thread) {
            plugin_log("Timer thread created");
        }
    }

    plugin_log("Initialized successfully");
}

/* ============================================================================
 * 第十九部分: 插件导出函数 —— plugin_on_response (必需)
 *
 * 每当 RCON 服务器返回数据时调用。
 * 所有插件都会收到所有响应。
 *
 * 职责:
 *   1. 清空 RCON 命令队列 (从 Logcore 线程传来的命令)
 *   2. 接收 license_key
 *   3. 解析聊天消息并分发命令
 * ============================================================================ */

__declspec(dllexport)
void plugin_on_response(const char *response) {
    /* 每次收到响应, 先清空命令队列 */
    flush_rcon_queue();

    if (!response || !response[0]) return;

    /* ===== 接收 license_key ===== */
    if (strncmp(response, "LICENSE_KEY:", 12) == 0) {
        strncpy(g_license_key, response + 12, sizeof(g_license_key) - 1);
        g_license_key[sizeof(g_license_key) - 1] = '\0';
        plugin_log("License key received");
        return;
    }

    /* 获取配置副本 */
    PluginConfig cfg;
    EnterCriticalSection(&g_config_lock);
    memcpy(&cfg, &g_config, sizeof(PluginConfig));
    LeaveCriticalSection(&g_config_lock);

    if (!cfg.enabled) return;

    /* ===== 解析聊天消息 ===== */
    /* 格式: [ChatAll] [SteamID:xxx steam: 76561198xxx] 玩家名 : 消息 */

    /* 1. 提取 SteamID */
    const char *pSteam = strstr(response, " steam: ");
    if (!pSteam) return;
    pSteam += 8;

    char steamid[MAX_STEAMID] = "";
    int i = 0;
    while (pSteam[i] && pSteam[i] != ' ' && pSteam[i] != ']' && i < MAX_STEAMID - 1) {
        steamid[i] = pSteam[i];
        i++;
    }
    steamid[i] = '\0';
    if (!steamid[0]) return;

    /* 2. 提取玩家名 (] 和 " : " 之间) */
    char player_name[MAX_NAME] = "";
    const char *pBracket = strchr(pSteam, ']');
    const char *pColon = strstr(response, " : ");
    if (pBracket && pColon && pColon > pBracket) {
        const char *start = pBracket + 1;
        while (*start == ' ' || *start == '\t') start++;
        int len = (int)(pColon - start);
        if (len > 0 && len < MAX_NAME) {
            strncpy(player_name, start, len);
            player_name[len] = '\0';
        }
    }
    if (player_name[0] == '\0') {
        strncpy(player_name, steamid, MAX_NAME - 1);
    }

    /* 3. 提取消息内容 */
    if (!pColon) return;
    const char *message = pColon + 3; /* 跳过 " : " */

    /* 去除首尾空白 */
    while (*message == ' ' || *message == '\t') message++;

    /* ===== 命令分发 (精确前缀匹配) ===== */
    if (match_trigger(message, cfg.trigger_score, cfg.trigger_score_count)) {
        handle_cmd_score(steamid);
        return;
    }
    if (match_trigger(message, cfg.trigger_signin, cfg.trigger_signin_count)) {
        handle_cmd_signin(steamid, player_name, &cfg);
        return;
    }
    if (match_trigger(message, cfg.trigger_top, cfg.trigger_top_count)) {
        handle_cmd_top(&cfg);
        return;
    }
    /* "kd" / "KD" 硬编码示例 */
    if (_stricmp(message, "kd") == 0) {
        handle_cmd_kd(steamid);
        return;
    }
}

/* ============================================================================
 * 第二十部分: 插件导出函数 —— plugin_on_config_change (可选)
 *
 * 当管理员在 Web 后台修改此插件的配置后, 主程序检测到变化并调用此函数。
 * config_json 为 GBK 编码的 JSON 字符串 (主程序已从 UTF-8 转换)。
 *
 * 检测周期默认 30 秒, 由 DEFAULT_CONFIG_CHECK_INTERVAL 控制。
 * ============================================================================ */

__declspec(dllexport)
void plugin_on_config_change(const char *config_json) {
    if (!config_json) return;
    plugin_log("Config change received");

    EnterCriticalSection(&g_config_lock);
    parse_config(config_json, &g_config);
    LeaveCriticalSection(&g_config_lock);

    plugin_log("Config applied");
}

/* ============================================================================
 * 第二十一部分: 插件导出函数 —— plugin_get_default_config (可选)
 *
 * 返回默认配置 JSON。插件首次激活时, 主程序调用此函数将默认配置写入 Redis。
 *
 * 返回值必须是静态字符串, 不要 malloc。
 * ============================================================================ */

__declspec(dllexport)
const char* plugin_get_default_config(void) {
    return DEFAULT_CONFIG;
}

/* ============================================================================
 * 第二十二部分: 插件导出函数 —— plugin_get_config_schema (可选)
 *
 * 返回配置项的描述信息 JSON, 用于 Web 管理后台自动生成配置表单。
 * 支持的 type: "bool", "int", "string", "array"
 * ============================================================================ */

__declspec(dllexport)
const char* plugin_get_config_schema(void) {
    return "{"
        "\"enabled\":{\"type\":\"bool\",\"desc\":\"\\u542f\\u7528\\u63d2\\u4ef6\"},"
        "\"kill_reward\":{\"type\":\"int\",\"desc\":\"\\u51fb\\u6740\\u5956\\u52b1\\u79ef\\u5206\"},"
        "\"death_penalty\":{\"type\":\"int\",\"desc\":\"\\u6b7b\\u4ea1\\u6263\\u9664\\u79ef\\u5206\"},"
        "\"signin_reward\":{\"type\":\"int\",\"desc\":\"\\u7b7e\\u5230\\u5956\\u52b1\"},"
        "\"signin_cooldown\":{\"type\":\"int\",\"desc\":\"\\u7b7e\\u5230\\u51b7\\u5374(\\u79d2)\"},"
        "\"auto_broadcast_interval\":{\"type\":\"int\",\"desc\":\"\\u81ea\\u52a8\\u5e7f\\u64ad\\u95f4\\u9694(\\u79d2),0=\\u5173\\u95ed\"},"
        "\"welcome_msg\":{\"type\":\"string\",\"desc\":\"\\u6b22\\u8fce/\\u5e7f\\u64ad\\u6d88\\u606f\"},"
        "\"trigger_score\":{\"type\":\"array\",\"desc\":\"\\u67e5\\u5206\\u89e6\\u53d1\\u8bcd\"},"
        "\"trigger_signin\":{\"type\":\"array\",\"desc\":\"\\u7b7e\\u5230\\u89e6\\u53d1\\u8bcd\"},"
        "\"trigger_top\":{\"type\":\"array\",\"desc\":\"\\u6392\\u884c\\u69c0\\u89e6\\u53d1\\u8bcd\"}"
    "}";
    /* schema 中的中文使用 \u 转义, 主程序会自动处理 */
}

/* ============================================================================
 * 第二十三部分: DLL 入口点
 *
 * DLL_PROCESS_DETACH 时必须:
 *   1. 注销 Logcore 回调 (否则 Logcore 调用已释放内存 → 崩溃)
 *   2. 停止定时线程
 *   3. 销毁临界区
 * ============================================================================ */

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) {
    (void)hinstDLL;
    (void)lpReserved;

    switch (fdwReason) {
    case DLL_PROCESS_ATTACH:
        break;

    case DLL_PROCESS_DETACH:
        /* 注销 Logcore 回调 (必须!) */
        if (g_register_log_callback) {
            g_register_log_callback(NULL);
        }

        /* 停止定时线程 */
        if (g_timer_running) {
            g_timer_running = 0;
            if (g_timer_thread) {
                WaitForSingleObject(g_timer_thread, 3000);
                CloseHandle(g_timer_thread);
                g_timer_thread = NULL;
            }
        }

        /* 销毁临界区 */
        if (g_initialized) {
            DeleteCriticalSection(&g_config_lock);
            DeleteCriticalSection(&g_players_lock);
            DeleteCriticalSection(&g_rcon_lock);
            g_initialized = 0;
        }

        plugin_log("Plugin unloaded");
        break;

    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
        break;
    }
    return TRUE;
}