#include "device_diag.h"
#include "device_debug_config.h"
#include "browser_open.h"
#include "mylog.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <stdint.h>
#include <fcntl.h>

#define DIAG_LOG_PASSWORD      "fcrs666"
#define DIAG_LOG_DIR           "/home/orangepi/car/master/log"
#define DIAG_LOG_TCP_PORT      18889
#define DIAG_LOG_MAX_FILES     2
#define DIAG_LOG_FILENAME_MAX  128
#define DIAG_LOG_PATH_MAX      512
#define DIAG_BROWSER_URL_MAX   512

static void diag_send_json(int sock, const struct sockaddr_in *addr, const char *json)
{
    if (!json)
        return;
    sendto(sock, json, strlen(json), 0,
           (const struct sockaddr *)addr, sizeof(*addr));
}

/* ===== 简易自检 ===== */

static int diag_check_camera(char *name, size_t name_sz, char *msg, size_t msg_sz)
{
    DIR *dir;
    struct dirent *ent;
    int found = 0;
    char path[320];  /* 容纳 /dev/ + d_name(最长255) */

    dir = opendir("/dev");
    if (!dir) {
        snprintf(name, name_sz, "未知");
        snprintf(msg, msg_sz, "无法打开 /dev");
        return 0;
    }

    while ((ent = readdir(dir)) != NULL) {
        if (strncmp(ent->d_name, "video", 5) != 0)
            continue;
        if (strstr(ent->d_name, "dec0") || strstr(ent->d_name, "enc0"))
            continue;
        size_t len = strlen(ent->d_name);
        if (len < 6)
            continue;
        int is_num = 1;
        for (size_t i = 5; i < len; i++) {
            if (ent->d_name[i] < '0' || ent->d_name[i] > '9') {
                is_num = 0;
                break;
            }
        }
        if (!is_num)
            continue;

        snprintf(path, sizeof(path), "/dev/%s", ent->d_name);
        int fd = open(path, O_RDWR);
        if (fd >= 0) {
            close(fd);
            snprintf(name, name_sz, "USB Camera");
            snprintf(msg, msg_sz, "/dev/%s", ent->d_name);
            found = 1;
            break;
        }
    }
    closedir(dir);

    if (!found) {
        snprintf(name, name_sz, "未检测到");
        snprintf(msg, msg_sz, "无可用 /dev/video* 设备");
    }
    return found;
}

static int diag_check_mic(char *name, size_t name_sz, char *msg, size_t msg_sz)
{
    FILE *fp;
    char line[512];
    int found = 0;

    fp = popen("arecord -l 2>/dev/null", "r");
    if (!fp) {
        snprintf(name, name_sz, "未知");
        snprintf(msg, msg_sz, "arecord 不可用");
        return 0;
    }

    while (fgets(line, sizeof(line), fp)) {
        if (strstr(line, "card") && strstr(line, "USB Audio")) {
            char *p = strstr(line, "card");
            snprintf(name, name_sz, "USB Audio");
            snprintf(msg, msg_sz, "%s", p ? p : "USB Audio");
            msg[strcspn(msg, "\n")] = '\0';
            found = 1;
            break;
        }
    }
    pclose(fp);

    if (!found) {
        snprintf(name, name_sz, "未检测到");
        snprintf(msg, msg_sz, "无 USB Audio 设备");
    }
    return found;
}

static int diag_check_speaker(char *name, size_t name_sz, char *msg, size_t msg_sz)
{
    FILE *fp;
    char line[512];
    int found = 0;

    fp = popen("aplay -l 2>/dev/null", "r");
    if (!fp) {
        snprintf(name, name_sz, "未知");
        snprintf(msg, msg_sz, "aplay 不可用");
        return 0;
    }

    while (fgets(line, sizeof(line), fp)) {
        if (strstr(line, "card") &&
            (strstr(line, "rk809") || strstr(line, "analog") ||
             strstr(line, "hdmi") || strstr(line, "USB Audio"))) {
            char *p = strstr(line, "card");
            snprintf(name, name_sz, "Audio Device");
            snprintf(msg, msg_sz, "%s", p ? p : "Audio Device");
            msg[strcspn(msg, "\n")] = '\0';
            found = 1;
            break;
        }
    }
    pclose(fp);

    if (!found) {
        snprintf(name, name_sz, "未检测到");
        snprintf(msg, msg_sz, "无音频输出设备");
    }
    return found;
}

static int diag_check_browser(char *name, size_t name_sz, char *msg, size_t msg_sz,
                              char *url, size_t url_sz)
{
    bool running = is_browser_running();
    if (running) {
        snprintf(name, name_sz, "Chromium");
        snprintf(msg, name_sz, "running");
        char tmp_url[DIAG_BROWSER_URL_MAX] = {0};
        bool tmp_running = false;
        browser_get_stream_url_info(tmp_url, sizeof(tmp_url), &tmp_running);
        snprintf(url, url_sz, "%s", tmp_url);
    } else {
        snprintf(name, name_sz, "未运行");
        snprintf(msg, msg_sz, "chromium 进程不存在");
        url[0] = '\0';
    }
    return running ? 1 : 0;
}

void device_diag_handle_check(int sock, const struct sockaddr_in *addr)
{
    cJSON *resp;
    cJSON *item;
    char *payload;
    char cam_name[64], cam_msg[128];
    char mic_name[64], mic_msg[128];
    char spk_name[64], spk_msg[128];
    char brw_name[64], brw_msg[128];
    char brw_url[DIAG_BROWSER_URL_MAX];

    int cam_ok = diag_check_camera(cam_name, sizeof(cam_name), cam_msg, sizeof(cam_msg));
    int mic_ok = diag_check_mic(mic_name, sizeof(mic_name), mic_msg, sizeof(mic_msg));
    int spk_ok = diag_check_speaker(spk_name, sizeof(spk_name), spk_msg, sizeof(spk_msg));
    int brw_ok = diag_check_browser(brw_name, sizeof(brw_name), brw_msg, sizeof(brw_msg),
                                    brw_url, sizeof(brw_url));

    resp = cJSON_CreateObject();
    cJSON_AddStringToObject(resp, "cmd", "device_check_response");

    item = cJSON_CreateObject();
    cJSON_AddBoolToObject(item, "detected", cam_ok ? 1 : 0);
    cJSON_AddStringToObject(item, "name", cam_name);
    cJSON_AddStringToObject(item, "message", cam_msg);
    cJSON_AddItemToObject(resp, "camera", item);

    item = cJSON_CreateObject();
    cJSON_AddBoolToObject(item, "detected", mic_ok ? 1 : 0);
    cJSON_AddStringToObject(item, "name", mic_name);
    cJSON_AddStringToObject(item, "message", mic_msg);
    cJSON_AddItemToObject(resp, "mic", item);

    item = cJSON_CreateObject();
    cJSON_AddBoolToObject(item, "detected", spk_ok ? 1 : 0);
    cJSON_AddStringToObject(item, "name", spk_name);
    cJSON_AddStringToObject(item, "message", spk_msg);
    cJSON_AddItemToObject(resp, "speaker", item);

    item = cJSON_CreateObject();
    cJSON_AddBoolToObject(item, "detected", brw_ok ? 1 : 0);
    cJSON_AddStringToObject(item, "name", brw_name);
    cJSON_AddStringToObject(item, "message", brw_msg);
    cJSON_AddItemToObject(resp, "browser", item);

    if (brw_url[0])
        cJSON_AddStringToObject(resp, "browserUrl", brw_url);

    payload = cJSON_PrintUnformatted(resp);
    if (payload) {
        diag_send_json(sock, addr, payload);
        free(payload);
    }
    cJSON_Delete(resp);
    my_zlog_info("设备调试: 自检完成 cam=%d mic=%d spk=%d brw=%d",
                 cam_ok, mic_ok, spk_ok, brw_ok);
}

/* ===== 浏览器控制 ===== */

void device_diag_handle_browser_ctrl(int sock, const struct sockaddr_in *addr, const char *op)
{
    cJSON *resp;
    char *payload;
    int success = 0;
    const char *msg = "";
    const char *cmd_name;

    resp = cJSON_CreateObject();

    if (strcmp(op, "refresh") == 0) {
        cmd_name = "browser_refresh_result";
        if (!is_browser_running()) {
            success = 0;
            msg = "刷新失败，浏览器未运行";
        } else {
            refresh_cam();
            success = 1;
            msg = "浏览器已刷新";
        }
    } else if (strcmp(op, "reopen") == 0) {
        cmd_name = "browser_reopen_result";
        char url[DIAG_BROWSER_URL_MAX] = {0};
        char effective[DIAG_BROWSER_URL_MAX] = {0};
        bool running = false;

        browser_get_stream_url_info(url, sizeof(url), &running);
        if (!running || !url[0]) {
            success = 0;
            msg = "重开失败，浏览器未运行或无 URL";
        } else {
            browser_begin_external_switch();
            { int _r = system("pkill -f chromium-browser 2>/dev/null"); (void)_r; }
            usleep(800000);
            if (browser_switch_stream_url(url, effective, sizeof(effective)) == 0) {
                success = 1;
                msg = "浏览器已重新打开";
            } else {
                success = 0;
                msg = "重开失败，URL 无效";
            }
            browser_end_external_switch();
        }
    } else {
        cmd_name = "browser_refresh_result";
        success = 0;
        msg = "未知操作";
    }

    cJSON_AddStringToObject(resp, "cmd", cmd_name);
    cJSON_AddBoolToObject(resp, "success", success ? 1 : 0);
    cJSON_AddStringToObject(resp, "message", msg);

    payload = cJSON_PrintUnformatted(resp);
    if (payload) {
        diag_send_json(sock, addr, payload);
        free(payload);
    }
    cJSON_Delete(resp);
    my_zlog_info("设备调试: 浏览器控制 op=%s success=%d", op, success);
}

/* ===== 日志获取 ===== */

typedef struct {
    char filename[DIAG_LOG_FILENAME_MAX];
    long size;
    time_t mtime;
} log_entry_t;

/* TCP 传输上下文：主线程已完成 socket+bind+listen，线程只做 accept+传输 */
typedef struct {
    int server_fd;
    log_entry_t entries[DIAG_LOG_MAX_FILES];
    int count;
} log_tcp_ctx_t;

static int diag_log_compare_mtime(const void *a, const void *b)
{
    const log_entry_t *ea = (const log_entry_t *)a;
    const log_entry_t *eb = (const log_entry_t *)b;
    if (ea->mtime > eb->mtime) return -1;
    if (ea->mtime < eb->mtime) return 1;
    return 0;
}

static int diag_find_recent_logs(log_entry_t *entries, int max_count)
{
    DIR *dir;
    struct dirent *ent;
    struct stat st;
    char filepath[DIAG_LOG_PATH_MAX];
    log_entry_t all[32];
    int count = 0;

    dir = opendir(DIAG_LOG_DIR);
    if (!dir) {
        my_zlog_error("设备调试: 无法打开日志目录 %s", DIAG_LOG_DIR);
        return 0;
    }

    while ((ent = readdir(dir)) != NULL && count < 32) {
        if (strncmp(ent->d_name, "log_", 4) != 0)
            continue;
        if (!strstr(ent->d_name, ".log"))
            continue;

        snprintf(filepath, sizeof(filepath), "%s/%s", DIAG_LOG_DIR, ent->d_name);
        if (stat(filepath, &st) != 0)
            continue;

        strncpy(all[count].filename, ent->d_name, DIAG_LOG_FILENAME_MAX - 1);
        all[count].filename[DIAG_LOG_FILENAME_MAX - 1] = '\0';
        all[count].size = (long)st.st_size;
        all[count].mtime = st.st_mtime;
        count++;
    }
    closedir(dir);

    if (count == 0)
        return 0;

    qsort(all, count, sizeof(log_entry_t), diag_log_compare_mtime);

    if (count > max_count)
        count = max_count;

    for (int i = 0; i < count; i++)
        entries[i] = all[i];

    return count;
}

/* TCP 线程：接收已 listen 的 server_fd，做 accept + 文件传输 */
static void *diag_log_tcp_thread(void *arg)
{
    log_tcp_ctx_t *ctx = (log_tcp_ctx_t *)arg;
    int server_fd = ctx->server_fd;
    int client_fd;
    struct timeval tv;
    char filename[DIAG_LOG_FILENAME_MAX + 2];

    /* accept 超时 30 秒 */
    tv.tv_sec = 30;
    tv.tv_usec = 0;
    setsockopt(server_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));

    my_zlog_info("设备调试: TCP 等待连接 port=%d", DIAG_LOG_TCP_PORT);

    client_fd = accept(server_fd, NULL, NULL);
    if (client_fd < 0) {
        my_zlog_error("设备调试: TCP accept 超时或失败");
        close(server_fd);
        free(ctx);
        return NULL;
    }

    my_zlog_info("设备调试: TCP 客户端已连接，开始传输");

    /* 循环接收文件名请求 */
    while (1) {
        ssize_t n;
        size_t flen = 0;
        int c;
        char ch;
        char filepath[DIAG_LOG_PATH_MAX];
        FILE *f;
        long fsize;
        uint8_t sizebuf[8];
        char buf[4096];
        size_t r;

        /* 逐字符读文件名直到 \n */
        while (flen < sizeof(filename) - 1) {
            n = recv(client_fd, &ch, 1, 0);
            if (n <= 0) {
                close(client_fd);
                close(server_fd);
                free(ctx);
                my_zlog_info("设备调试: TCP 日志传输完成");
                return NULL;
            }
            if (ch == '\n')
                break;
            filename[flen++] = ch;
        }
        filename[flen] = '\0';

        /* 安全校验：文件名只能包含字母数字下划线和点 */
        int valid = 1;
        for (size_t i = 0; i < flen; i++) {
            c = (unsigned char)filename[i];
            if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
                  (c >= '0' && c <= '9') || c == '_' || c == '-' || c == '.')) {
                valid = 0;
                break;
            }
        }
        if (!valid || flen == 0) {
            my_zlog_error("设备调试: 非法文件名请求 '%s'", filename);
            memset(sizebuf, 0, 8);
            send(client_fd, sizebuf, 8, 0);
            continue;
        }

        snprintf(filepath, sizeof(filepath), "%s/%s", DIAG_LOG_DIR, filename);
        f = fopen(filepath, "rb");
        if (!f) {
            my_zlog_error("设备调试: 日志文件不存在 %s", filepath);
            memset(sizebuf, 0, 8);
            send(client_fd, sizebuf, 8, 0);
            continue;
        }

        fseek(f, 0, SEEK_END);
        fsize = ftell(f);
        rewind(f);

        /* 8 字节大端序发送文件大小 */
        uint64_t sz = (uint64_t)fsize;
        for (int i = 7; i >= 0; i--) {
            sizebuf[i] = (uint8_t)(sz & 0xFF);
            sz >>= 8;
        }
        send(client_fd, sizebuf, 8, 0);

        /* 发送文件内容 */
        while ((r = fread(buf, 1, sizeof(buf), f)) > 0) {
            size_t sent = 0;
            while (sent < r) {
                ssize_t s = send(client_fd, buf + sent, r - sent, 0);
                if (s <= 0)
                    break;
                sent += s;
            }
            if (sent < r)
                break;
        }
        fclose(f);
        my_zlog_info("设备调试: 已发送日志 %s (%ld bytes)", filename, fsize);
    }

    close(client_fd);
    close(server_fd);
    free(ctx);
    return NULL;
}

void device_diag_handle_log_fetch(int sock, const struct sockaddr_in *addr, cJSON *root)
{
    cJSON *pwd_item;
    const char *password = NULL;
    cJSON *resp;
    cJSON *arr;
    char *payload;
    log_entry_t entries[DIAG_LOG_MAX_FILES];
    int n;
    int server_fd;
    struct sockaddr_in tcp_addr;
    int opt = 1;
    pthread_t tid;
    pthread_attr_t attr;
    log_tcp_ctx_t *ctx;

    pwd_item = cJSON_GetObjectItemCaseSensitive(root, "password");
    if (!cJSON_IsString(pwd_item) || !pwd_item->valuestring) {
        password = "";
    } else {
        password = pwd_item->valuestring;
    }

    /* 密码校验 */
    if (strcmp(password, DIAG_LOG_PASSWORD) != 0) {
        resp = cJSON_CreateObject();
        cJSON_AddStringToObject(resp, "cmd", "log_fetch_ready");
        cJSON_AddNumberToObject(resp, "tcpPort", 0);
        cJSON_AddItemToObject(resp, "files", cJSON_CreateArray());
        cJSON_AddStringToObject(resp, "message", "密码错误");
        payload = cJSON_PrintUnformatted(resp);
        if (payload) {
            diag_send_json(sock, addr, payload);
            free(payload);
        }
        cJSON_Delete(resp);
        my_zlog_warn("设备调试: log_fetch 密码错误");
        return;
    }

    /* 查找最近 2 个日志 */
    n = diag_find_recent_logs(entries, DIAG_LOG_MAX_FILES);
    if (n <= 0) {
        resp = cJSON_CreateObject();
        cJSON_AddStringToObject(resp, "cmd", "log_fetch_ready");
        cJSON_AddNumberToObject(resp, "tcpPort", 0);
        cJSON_AddItemToObject(resp, "files", cJSON_CreateArray());
        cJSON_AddStringToObject(resp, "message", "设备无日志文件");
        payload = cJSON_PrintUnformatted(resp);
        if (payload) {
            diag_send_json(sock, addr, payload);
            free(payload);
        }
        cJSON_Delete(resp);
        my_zlog_warn("设备调试: 无日志文件可发送");
        return;
    }

    /* 关键修复：先创建 TCP server 并 listen 成功，再回 log_fetch_ready。
     * 避免"手机收到 ready 后立即连接，但设备还没 listen，连接被拒绝"的时序问题。 */
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd < 0) {
        my_zlog_error("设备调试: TCP socket 创建失败");
        return;
    }

    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    memset(&tcp_addr, 0, sizeof(tcp_addr));
    tcp_addr.sin_family = AF_INET;
    tcp_addr.sin_port = htons(DIAG_LOG_TCP_PORT);
    tcp_addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(server_fd, (struct sockaddr *)&tcp_addr, sizeof(tcp_addr)) < 0) {
        my_zlog_error("设备调试: TCP bind %d 失败", DIAG_LOG_TCP_PORT);
        close(server_fd);
        return;
    }

    if (listen(server_fd, 1) < 0) {
        my_zlog_error("设备调试: TCP listen 失败");
        close(server_fd);
        return;
    }

    my_zlog_info("设备调试: TCP 已 listen port=%d，准备回 log_fetch_ready", DIAG_LOG_TCP_PORT);

    /* 回 log_fetch_ready（此时 listen 已就绪，手机可立即连接） */
    resp = cJSON_CreateObject();
    cJSON_AddStringToObject(resp, "cmd", "log_fetch_ready");
    cJSON_AddNumberToObject(resp, "tcpPort", DIAG_LOG_TCP_PORT);
    arr = cJSON_CreateArray();
    for (int i = 0; i < n; i++) {
        cJSON *item = cJSON_CreateObject();
        cJSON_AddStringToObject(item, "name", entries[i].filename);
        cJSON_AddNumberToObject(item, "size", entries[i].size);
        cJSON_AddItemToArray(arr, item);
    }
    cJSON_AddItemToObject(resp, "files", arr);

    payload = cJSON_PrintUnformatted(resp);
    if (payload) {
        diag_send_json(sock, addr, payload);
        free(payload);
    }
    cJSON_Delete(resp);

    /* 启动 detached 线程做 accept + 传输 */
    ctx = (log_tcp_ctx_t *)malloc(sizeof(log_tcp_ctx_t));
    if (!ctx) {
        my_zlog_error("设备调试: malloc 失败");
        close(server_fd);
        return;
    }
    ctx->server_fd = server_fd;
    ctx->count = n;
    for (int i = 0; i < n; i++)
        ctx->entries[i] = entries[i];

    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    if (pthread_create(&tid, &attr, diag_log_tcp_thread, ctx) != 0) {
        my_zlog_error("设备调试: TCP 线程创建失败");
        free(ctx);
        close(server_fd);
        pthread_attr_destroy(&attr);
        return;
    }
    pthread_attr_destroy(&attr);

    my_zlog_info("设备调试: log_fetch 已启动 TCP %d，共 %d 个日志", DIAG_LOG_TCP_PORT, n);
}

int device_diag_is_cmd(const char *cmd)
{
    if (!cmd)
        return 0;
    if (strcmp(cmd, "device_check") == 0 ||
        strcmp(cmd, "browser_refresh") == 0 ||
        strcmp(cmd, "browser_reopen") == 0 ||
        strcmp(cmd, "log_fetch") == 0)
        return 1;
    return 0;
}
