#include "browser_open.h"
#include "webrtcpush_run.h"
#include "device_fileopen.h"
#include "device_identity.h"
#include "http_config_mqtt.h"
#include "pthrpoll.h"
#include "delay.h"

#include <dirent.h>

static char s_webcam[2048];//存放启动火狐网站命令
static char s_active_stream_url[768]; /* 最近一次启动 chromium 的完整推流网址 */
static int s_open_cam_enter_count = 0;      // 记录 opencamsh 进入次数
static int s_open_cam_fail_streak = 0;      // 连续启动失败次数
static int s_refresh_cam_enter_count = 0;   // 记录 refresh_cam 进入次数
static int s_refresh_cam_fail_count = 0;    // 刷新失败累计次数

#define REFRESH_CAM_COOLDOWN_SEC 20

static pthread_mutex_t s_refresh_stat_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t s_refresh_run_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t s_refresh_cool_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t s_refresh_pool_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t s_browser_ops_mutex = PTHREAD_MUTEX_INITIALIZER;
static time_t s_refresh_next_allowed = 0; /* time(NULL) 之前不允许再提交刷新 */
static time_t s_browser_retry_after = 0;
static volatile int s_browser_external_switch = 0;
static ThreadPool_t *s_refresh_pool = NULL;

#define BROWSER_AUTO_RESTART_FAIL_SEC 5

static int browser_launch_chromium(const char *launch_url);
static int browser_chromium_found_cb(int pid, const char *cmdline, void *ctx);

int  opencamsh() {
    char urls[512];
    int rc;

    s_open_cam_enter_count++;
    my_zlog_info("opencamsh enter count:%d", s_open_cam_enter_count);

    snprintf(urls, sizeof(urls), "%s%s", g_mqtt_cam_config_t->videourl, mqtt_topic_pure_number());

    pthread_mutex_lock(&s_browser_ops_mutex);
    if (s_browser_external_switch) {
        pthread_mutex_unlock(&s_browser_ops_mutex);
        my_zlog_debug("opencamsh skipped: external stream switch in progress");
        return -1;
    }

    rc = browser_launch_chromium(urls);
    pthread_mutex_unlock(&s_browser_ops_mutex);

    if (rc != 0) {
        s_open_cam_fail_streak++;
        s_browser_retry_after = time(NULL) + BROWSER_AUTO_RESTART_FAIL_SEC;
        if (s_open_cam_fail_streak >= 10 && (s_open_cam_fail_streak % 10) == 0) {
            my_zlog_error("chromium start fail, continuous fail:%d, enter count:%d",
                          s_open_cam_fail_streak, s_open_cam_enter_count);
        } else {
            my_zlog_warn("chromium start fail, continuous fail:%d, enter count:%d",
                         s_open_cam_fail_streak, s_open_cam_enter_count);
        }
        return -1;
    }

    if (s_open_cam_fail_streak > 0) {
        my_zlog_info("chromium start recovered, previous continuous fail:%d", s_open_cam_fail_streak);
    }
    s_open_cam_fail_streak = 0;
    s_browser_retry_after = 0;
    my_zlog_debug("open cam");
    return 0;
}

/* 实际刷新逻辑：在后台线程执行，避免阻塞 MQTT 心跳线程 */
static void refresh_cam_impl(void)
{
    int refreshed_windows = 0;
    int failed_windows = 0;
    int enter = 0;

    pthread_mutex_lock(&s_refresh_stat_mutex);
    s_refresh_cam_enter_count++;
    enter = s_refresh_cam_enter_count;
    pthread_mutex_unlock(&s_refresh_stat_mutex);

    my_zlog_info("refresh_cam(async worker): start enter=%d", enter);

    setenv("DISPLAY", ":0", 1); // 确保 xdotool 在图形会话中执行
    setenv("XAUTHORITY", "/home/orangepi/.Xauthority", 1);//加入授权

    const char *search_command = "xdotool search --class \"chromium-browser\"";//获取窗口id

    FILE *fp = popen(search_command, "r");

    if (!fp) {
        pthread_mutex_lock(&s_refresh_stat_mutex);
        s_refresh_cam_fail_count++;
        int fc = s_refresh_cam_fail_count;
        pthread_mutex_unlock(&s_refresh_stat_mutex);
        my_zlog_error("refresh_cam outcome=FAIL reason=popen total_fail=%d enter=%d",
                      fc, enter);
        return;
    }

    char window_id[32];

    while (fgets(window_id, sizeof(window_id), fp) != NULL) {
        window_id[strcspn(window_id, "\n")] = '\0';

        char activate_command[128];

        snprintf(activate_command, sizeof(activate_command), "xdotool windowactivate %s key Ctrl+r", window_id);

        if (system(activate_command) == 0) {
            refreshed_windows++;
        } else {
            failed_windows++;
            my_zlog_warn("refresh_cam window command fail, window id:%s", window_id);
        }
    }

    pclose(fp);

    if (refreshed_windows == 0) {
        pthread_mutex_lock(&s_refresh_stat_mutex);
        s_refresh_cam_fail_count++;
        int fc = s_refresh_cam_fail_count;
        pthread_mutex_unlock(&s_refresh_stat_mutex);
        my_zlog_warn("refresh_cam outcome=FAIL reason=no_chromium_window total_fail=%d enter=%d",
                     fc, enter);
        return;
    }

    if (failed_windows > 0) {
        pthread_mutex_lock(&s_refresh_stat_mutex);
        s_refresh_cam_fail_count++;
        int fc = s_refresh_cam_fail_count;
        pthread_mutex_unlock(&s_refresh_stat_mutex);
        my_zlog_warn("refresh_cam outcome=FAIL reason=partial success=%d fail=%d total_fail=%d enter=%d",
                     refreshed_windows, failed_windows, fc, enter);
        return;
    }

    my_zlog_info("refresh_cam outcome=OK windows=%d enter=%d", refreshed_windows, enter);
}

static int ensure_refresh_pool_ready(void)
{
    pthread_mutex_lock(&s_refresh_pool_mutex);
    if (s_refresh_pool == NULL) {
        s_refresh_pool = thread_pool_init(1, 1);
        if (s_refresh_pool == NULL) {
            pthread_mutex_unlock(&s_refresh_pool_mutex);
            return -1;
        }
    }
    pthread_mutex_unlock(&s_refresh_pool_mutex);
    return 0;
}

static void refresh_cam_worker_task(void *arg)
{
    (void)arg;
    pthread_mutex_lock(&s_refresh_run_mutex);
    refresh_cam_impl();
    pthread_mutex_unlock(&s_refresh_run_mutex);
}

void refresh_cam(void)
{//刷新页面：异步提交，不阻塞 MQTT 收发线程
    pthread_mutex_lock(&s_refresh_cool_mutex);
    {
        time_t now = time(NULL);
        if (now < s_refresh_next_allowed) {
            long left = (long)(s_refresh_next_allowed - now);
            pthread_mutex_unlock(&s_refresh_cool_mutex);
            my_zlog_debug("refresh_cam skipped: cooldown %lds remaining (max once per %ds)",
                           left, REFRESH_CAM_COOLDOWN_SEC);
            return;
        }
    }
    pthread_mutex_unlock(&s_refresh_cool_mutex);

    /* GStreamer：软刷新（保留 WS），避免进房验证把流打断 */
    if (webrtcpush_should_use_mpp()) {
        if (!webrtcpush_is_running()) {
            my_zlog_warn("refresh_cam webrtcpush: not running (wait thread_open_browser restart)");
            return;
        }
        webrtcpush_request_soft_refresh();
        pthread_mutex_lock(&s_refresh_cool_mutex);
        s_refresh_next_allowed = time(NULL) + REFRESH_CAM_COOLDOWN_SEC;
        pthread_mutex_unlock(&s_refresh_cool_mutex);
        my_zlog_info("refresh_cam outcome=PENDING (webrtcpush soft refresh, cooldown=%ds)",
                     REFRESH_CAM_COOLDOWN_SEC);
        return;
    }

    if (ensure_refresh_pool_ready() != 0) {
        my_zlog_error("refresh_cam outcome=FAIL reason=thread_pool_init errno=%s (未执行刷新)",
                      strerror(errno));
        return;
    }

    if (thread_pool_add_task(s_refresh_pool, refresh_cam_worker_task, NULL) != 0) {
        my_zlog_error("refresh_cam outcome=FAIL reason=thread_pool_add_task errno=%s (未执行刷新)",
                      strerror(errno));
        return;
    }

    pthread_mutex_lock(&s_refresh_cool_mutex);
    s_refresh_next_allowed = time(NULL) + REFRESH_CAM_COOLDOWN_SEC;
    pthread_mutex_unlock(&s_refresh_cool_mutex);

    my_zlog_info("refresh_cam outcome=PENDING (async submitted, mqtt thread not blocked, cooldown=%ds)",
                 REFRESH_CAM_COOLDOWN_SEC);
}

// 检查Chromium进程是否已运行（直接扫 /proc，避免 UDP 线程 popen 死锁）
static int browser_read_pid_cmdline(int pid, char *cmdline, size_t cmdline_size)
{
    char path[64];
    FILE *cf;
    size_t n;
    size_t i;

    snprintf(path, sizeof(path), "/proc/%d/cmdline", pid);
    cf = fopen(path, "r");
    if (!cf) {
        return -1;
    }
    n = fread(cmdline, 1, cmdline_size - 1, cf);
    fclose(cf);
    if (n == 0) {
        return -1;
    }
    cmdline[n] = '\0';
    for (i = 0; i < n; i++) {
        if (cmdline[i] == '\0') {
            cmdline[i] = ' ';
        }
    }
    return 0;
}

static int browser_is_chromium_cmdline(const char *cmdline)
{
    if (!cmdline || !cmdline[0]) {
        return 0;
    }
    if (strstr(cmdline, "chromium-browser") != NULL) {
        return 1;
    }
    if (strstr(cmdline, "/chromium-browser") != NULL) {
        return 1;
    }
    return 0;
}

/* 仅认推流主进程，排除 zygote/gpu 等子进程 */
static int browser_is_stream_chromium_cmdline(const char *cmdline)
{
    if (!browser_is_chromium_cmdline(cmdline)) {
        return 0;
    }
    if (strstr(cmdline, "--type=") != NULL) {
        return 0;
    }
    if (strstr(cmdline, "--use-fake-ui-for-media-stream") != NULL) {
        return 1;
    }
    if (strstr(cmdline, "https://") != NULL || strstr(cmdline, "http://") != NULL) {
        return 1;
    }
    return 0;
}

void browser_begin_external_switch(void)
{
    pthread_mutex_lock(&s_browser_ops_mutex);
    s_browser_external_switch = 1;
    pthread_mutex_unlock(&s_browser_ops_mutex);
}

void browser_end_external_switch(void)
{
    pthread_mutex_lock(&s_browser_ops_mutex);
    s_browser_external_switch = 0;
    pthread_mutex_unlock(&s_browser_ops_mutex);
}

int browser_auto_restart_allowed(void)
{
    time_t now = time(NULL);

    pthread_mutex_lock(&s_browser_ops_mutex);
    if (s_browser_external_switch) {
        pthread_mutex_unlock(&s_browser_ops_mutex);
        return 0;
    }
    if (s_browser_retry_after > 0 && now < s_browser_retry_after) {
        pthread_mutex_unlock(&s_browser_ops_mutex);
        return 0;
    }
    pthread_mutex_unlock(&s_browser_ops_mutex);
    return 1;
}

typedef int (*browser_chromium_cb)(int pid, const char *cmdline, void *ctx);

static int browser_chromium_url_cb(int pid, const char *cmdline, void *ctx);

static int browser_foreach_chromium(browser_chromium_cb cb, void *ctx)
{
    DIR *dir;
    struct dirent *ent;

    if (!cb) {
        return -1;
    }

    dir = opendir("/proc");
    if (!dir) {
        return -1;
    }

    while ((ent = readdir(dir)) != NULL) {
        char cmdline[2048];
        int pid;
        int rc;

        if (ent->d_name[0] < '1' || ent->d_name[0] > '9') {
            continue;
        }
        pid = atoi(ent->d_name);
        if (pid <= 0) {
            continue;
        }
        if (browser_read_pid_cmdline(pid, cmdline, sizeof(cmdline)) != 0) {
            continue;
        }
        if (!browser_is_chromium_cmdline(cmdline)) {
            continue;
        }
        if (cb == browser_chromium_found_cb && !browser_is_stream_chromium_cmdline(cmdline)) {
            continue;
        }
        rc = cb(pid, cmdline, ctx);
        if (rc != 0) {
            closedir(dir);
            return rc;
        }
    }

    closedir(dir);
    return 0;
}

static int browser_chromium_found_cb(int pid, const char *cmdline, void *ctx)
{
    (void)pid;
    (void)cmdline;
    *(bool *)ctx = true;
    return 1;
}

bool is_browser_running() {
    bool running = false;
    (void)browser_foreach_chromium(browser_chromium_found_cb, &running);
    return running;
}

static void browser_prepare_display_env(void)
{
    setenv("DISPLAY", ":0", 1);
    setenv("XAUTHORITY", "/home/orangepi/.Xauthority", 1);
}

static void browser_run_shell_cmd(const char *cmd)
{
    int status;

    if (!cmd || !cmd[0]) {
        return;
    }
    status = system(cmd);
    if (status != 0) {
        my_zlog_debug("browser shell cmd status=%d: %s", status, cmd);
    }
}

static int browser_kill_chromium(void)
{
    int i;

    for (i = 0; i < 12; i++) {
        if (!is_browser_running()) {
            return 0;
        }

        if (i < 3) {
            browser_run_shell_cmd("pkill -TERM -f 'chromium-browser --use-fake-ui' >/dev/null 2>&1");
            browser_run_shell_cmd("pkill -TERM -f 'chromium-browser.*http' >/dev/null 2>&1");
            browser_run_shell_cmd("su - orangepi -c \"pkill -TERM -f 'chromium-browser --use-fake-ui'; pkill -TERM chromium\" >/dev/null 2>&1");
        } else {
            browser_run_shell_cmd("pkill -9 -f 'chromium-browser --use-fake-ui' >/dev/null 2>&1");
            browser_run_shell_cmd("pkill -9 -f 'chromium-browser.*http' >/dev/null 2>&1");
            browser_run_shell_cmd("su - orangepi -c \"pkill -9 -f 'chromium-browser --use-fake-ui'; pkill -9 chromium\" >/dev/null 2>&1");
        }
        delay_ms(250);
    }

    return is_browser_running() ? -1 : 0;
}

static int browser_wait_chromium_running(int timeout_ms)
{
    int elapsed = 0;

    while (elapsed < timeout_ms) {
        if (is_browser_running()) {
            return 0;
        }
        delay_ms(200);
        elapsed += 200;
    }
    return -1;
}

static int browser_read_url_from_proc(char *url, size_t url_size)
{
    struct {
        char *url;
        size_t url_size;
        int found;
    } ctx;

    if (!url || url_size == 0) {
        return -1;
    }

    ctx.url = url;
    ctx.url_size = url_size;
    ctx.found = 0;

    browser_foreach_chromium(browser_chromium_url_cb, &ctx);
    return ctx.found ? 0 : -1;
}

static int browser_chromium_url_cb(int pid, const char *cmdline, void *ctx)
{
    struct {
        char *url;
        size_t url_size;
        int found;
    } *search = ctx;
    const char *http_pos;
    size_t len;

    (void)pid;
    http_pos = strstr(cmdline, "https://");
    if (!http_pos) {
        http_pos = strstr(cmdline, "http://");
    }
    if (!http_pos) {
        return 0;
    }

    len = 0;
    while (http_pos[len] != '\0' && http_pos[len] != ' ' && len + 1 < search->url_size) {
        len++;
    }
    memcpy(search->url, http_pos, len);
    search->url[len] = '\0';
    search->found = 1;
    return 1;
}

int browser_get_stream_url_info(char *url, size_t url_size, bool *running)
{
    bool chromium_running = is_browser_running();

    if (running) {
        *running = chromium_running;
    }

    if (!url || url_size == 0) {
        return chromium_running ? 0 : -1;
    }

    url[0] = '\0';
    if (!chromium_running) {
        return 0;
    }

    if (s_active_stream_url[0] != '\0') {
        snprintf(url, url_size, "%s", s_active_stream_url);
        return 0;
    }

    if (browser_read_url_from_proc(url, url_size) == 0) {
        return 0;
    }

    if (g_mqtt_cam_config_t && g_mqtt_cam_config_t->videourl[0] != '\0') {
        const char *device_no = mqtt_topic_pure_number();
        snprintf(url, url_size, "%s%s",
                 g_mqtt_cam_config_t->videourl,
                 device_no ? device_no : "");
    }
    return 0;
}

static void shell_escape_single_quoted(const char *input, char *out, size_t out_size)
{
    size_t i = 0;
    size_t j = 0;

    if (!out || out_size == 0) {
        return;
    }
    out[0] = '\0';
    if (!input) {
        return;
    }

    for (i = 0; input[i] != '\0' && j + 4 < out_size; i++) {
        if (input[i] == '\'') {
            out[j++] = '\'';
            out[j++] = '\\';
            out[j++] = '\'';
            out[j++] = '\'';
        } else {
            out[j++] = input[i];
        }
    }
    out[j] = '\0';
}

static int browser_validate_stream_url(const char *url)
{
    size_t len;

    if (!url || !url[0]) {
        return -1;
    }
    len = strlen(url);
    if (len >= sizeof(g_mqtt_cam_config_t->videourl)) {
        return -1;
    }
    if (strncmp(url, "http://", 7) != 0 && strncmp(url, "https://", 8) != 0) {
        return -1;
    }
    return 0;
}

static void browser_build_launch_url(const char *url, char *launch_url, size_t launch_size)
{
    size_t len;
    const char *device_no;

    if (!url || !launch_url || launch_size == 0) {
        return;
    }

    len = strlen(url);
    device_no = mqtt_topic_pure_number();
    if (len > 0 && url[len - 1] == '=') {
        snprintf(launch_url, launch_size, "%s%s", url, device_no ? device_no : "");
        return;
    }
    if (len >= 5 && strcmp(url + len - 5, "?dev=") == 0) {
        snprintf(launch_url, launch_size, "%s%s", url, device_no ? device_no : "");
        return;
    }

    snprintf(launch_url, launch_size, "%s", url);
}

static int browser_launch_chromium(const char *launch_url)
{
    char escaped_url[1024];
    char cmd[2048];
    int ret;

    if (!launch_url || !launch_url[0]) {
        return -1;
    }

    browser_prepare_display_env();
    shell_escape_single_quoted(launch_url, escaped_url, sizeof(escaped_url));
    snprintf(cmd, sizeof(cmd),
             "su - orangepi -c 'export DISPLAY=:0 XAUTHORITY=/home/orangepi/.Xauthority; "
             "nohup chromium-browser --use-fake-ui-for-media-stream %s "
             ">/tmp/chromium-stream.log 2>&1 &'",
             escaped_url);

    my_zlog_info("browser_launch_chromium: %s", launch_url);
    ret = system(cmd);
    if (ret != 0) {
        my_zlog_error("browser_launch_chromium failed ret=%d", ret);
        return -1;
    }

    if (browser_wait_chromium_running(5000) != 0) {
        my_zlog_error("browser_launch_chromium: chromium not running after launch");
        return -1;
    }

    strncpy(s_active_stream_url, launch_url, sizeof(s_active_stream_url) - 1);
    s_active_stream_url[sizeof(s_active_stream_url) - 1] = '\0';
    return 0;
}

int browser_switch_stream_url(const char *url, char *effective_url, size_t effective_size)
{
    char launch_url[768];
    int ret;

    if (browser_validate_stream_url(url) != 0) {
        my_zlog_error("browser_switch_stream_url: invalid url");
        return -1;
    }

    browser_build_launch_url(url, launch_url, sizeof(launch_url));
    strncpy(g_mqtt_cam_config_t->videourl, url, sizeof(g_mqtt_cam_config_t->videourl) - 1);
    g_mqtt_cam_config_t->videourl[sizeof(g_mqtt_cam_config_t->videourl) - 1] = '\0';

    my_zlog_info("browser_switch_stream_url: kill old chromium, launch %s", launch_url);
    browser_begin_external_switch();
    if (browser_kill_chromium() != 0) {
        my_zlog_warn("browser_switch_stream_url: old chromium may still be exiting, continue launch");
    }
    delay_ms(1000);

    pthread_mutex_lock(&s_browser_ops_mutex);
    ret = browser_launch_chromium(launch_url);
    pthread_mutex_unlock(&s_browser_ops_mutex);
    browser_end_external_switch();

    if (ret != 0) {
        s_browser_retry_after = time(NULL) + BROWSER_AUTO_RESTART_FAIL_SEC;
        return -1;
    }

    if (effective_url && effective_size > 0) {
        snprintf(effective_url, effective_size, "%s", launch_url);
    }

    my_zlog_info("browser_switch_stream_url: ok %s", launch_url);
    return 0;
}
