#include "runtime_deps.h"

#include "mylog.h"
#include "webrtcpush_config.h"

#include <gst/gst.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

typedef struct {
  const char *pkg;
  const char *pin; /* 缺包时优先安装的参考版本；已安装任意版本均可 */
} DebPkgPin;

typedef struct {
  const char *plugin; /* gst_registry_find_plugin 名称 */
  const char *element; /* 额外用 factory_make 探测（可为 NULL） */
} GstNeed;

/* 参考板 orangepi3b @ 5.10.160-rockchip-rk356x */
static const DebPkgPin s_webrtc_debs[] = {
    {"gstreamer1.0-nice", "0.1.18-2"},
    {"gstreamer1.0-pulseaudio", "1.20.3-0ubuntu1"},
    {"librockchip-mpp1", "1.5.0-2"},
    {"gstreamer1.0-rockchip1", "1.14-4"},
    {"gstreamer1.0-plugins-base", "1.20.3-1"},
    {"gstreamer1.0-plugins-good", "1.20.3-0ubuntu1"},
    {"gstreamer1.0-plugins-bad", "1.20.3-0ubuntu1"},
    {"libgstreamer-plugins-bad1.0-0", "1.20.3-0ubuntu1"},
};

static const DebPkgPin s_browser_debs[] = {
    {"chromium-browser", "1:110.0.5481.4-0ubuntu1~jammy~rkmpp20230130"},
    {"xdotool", "1:3.20160805.1-4"},
};

static const GstNeed s_webrtc_gst_required[] = {
    {"nice", NULL},
    {"webrtc", "webrtcbin"},
    {"video4linux2", "v4l2src"},
    {"opus", "opusenc"},
};

static gboolean s_webrtc_push_ok = FALSE;
static gboolean s_webrtc_push_disabled = FALSE;

/* GLib/GObject/GStreamer 日志重定向到 zlog
 * 平台底层库(libsoup/libnice/GStreamer)的 INFO/MESSAGE/DEBUG 级别
 * 通常是 WebSocket 心跳帧/消息分发等 3s 一次的常规事件,
 * 下沉到 debug 后 release 发布版(LOG_PRODUCTION)自动屏蔽,
 * 避免日志污染。ERROR/WARNING 保留。 */
static void glib_log_handler(const gchar *log_domain, GLogLevelFlags log_level,
                             const gchar *message, gpointer user_data)
{
  (void)user_data;
  if (log_level & G_LOG_LEVEL_ERROR)
    my_zlog_error("glib/%s: %s", log_domain ? log_domain : "?", message ? message : "(null)");
  else if (log_level & (G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING))
    my_zlog_warn("glib/%s: %s", log_domain ? log_domain : "?", message ? message : "(null)");
  else
    my_zlog_debug("glib/%s: %s", log_domain ? log_domain : "?", message ? message : "(null)");
}

static const char *s_chromium_bins[] = {
    "/usr/bin/chromium-browser",
    "/usr/bin/chromium",
};

static int dpkg_installed_version(const char *pkg, char *ver, size_t ver_len)
{
  FILE *f;
  char line[512];
  int in_pkg = 0;

  if (!pkg || !ver || ver_len == 0)
    return -1;
  ver[0] = '\0';

  f = fopen("/var/lib/dpkg/status", "r");
  if (!f)
    return -1;

  while (fgets(line, sizeof(line), f)) {
    if (strncmp(line, "Package: ", 9) == 0) {
      const char *name = line + 9;
      size_t n = strcspn(name, "\n");
      char pname[128];
      if (n >= sizeof(pname))
        n = sizeof(pname) - 1;
      memcpy(pname, name, n);
      pname[n] = '\0';
      in_pkg = (strcmp(pname, pkg) == 0);
    } else if (in_pkg && strncmp(line, "Version: ", 9) == 0) {
      const char *v = line + 9;
      size_t n = strcspn(v, "\n");
      if (n >= ver_len)
        n = ver_len - 1;
      memcpy(ver, v, n);
      ver[n] = '\0';
      fclose(f);
      return 0;
    } else if (line[0] == '\n') {
      in_pkg = 0;
    }
  }

  fclose(f);
  return -1;
}

static gboolean deb_pkg_ok(const DebPkgPin *e)
{
  char installed[128];

  if (dpkg_installed_version(e->pkg, installed, sizeof(installed)) != 0)
    return FALSE;
  return installed[0] != '\0';
}

static int run_apt(const char *args)
{
  char cmd[768];
  const char *priv = (geteuid() == 0) ? "" : "sudo ";

  snprintf(cmd, sizeof(cmd), "%sDEBIAN_FRONTEND=noninteractive %s", priv, args);
  my_zlog_info("runtime_deps: %s", cmd);
  return system(cmd);
}

static int apt_install_one(const DebPkgPin *e)
{
  char spec[192];
  char args[256];
  int rc;

  if (e->pin && e->pin[0]) {
    snprintf(spec, sizeof(spec), "%s=%s", e->pkg, e->pin);
    snprintf(args, sizeof(args),
        "apt-get install -y --no-install-recommends %s", spec);
    rc = run_apt(args);
    if (rc == 0)
      return 0;
    my_zlog_warn("runtime_deps: pinned install failed %s, trying latest", spec);
  }

  snprintf(args, sizeof(args), "apt-get install -y --no-install-recommends %s", e->pkg);
  return run_apt(args);
}

static int ensure_deb_list(const DebPkgPin *list, size_t n)
{
  gboolean need_update = FALSE;
  size_t i;

  for (i = 0; i < n; i++) {
    if (!deb_pkg_ok(&list[i])) {
      char ver[128];
      if (dpkg_installed_version(list[i].pkg, ver, sizeof(ver)) == 0)
        my_zlog_warn("runtime_deps: %s installed=%s want=%s",
            list[i].pkg, ver, list[i].pin ? list[i].pin : "(any)");
      else
        my_zlog_warn("runtime_deps: missing package %s (pin %s)",
            list[i].pkg, list[i].pin ? list[i].pin : "(any)");
      need_update = TRUE;
    }
  }

  if (!need_update) {
    my_zlog_info("runtime_deps: deb packages OK");
    return 0;
  }

  if (run_apt("apt-get update") != 0)
    my_zlog_warn("runtime_deps: apt-get update failed");

  for (i = 0; i < n; i++) {
    if (deb_pkg_ok(&list[i]))
      continue;
    if (apt_install_one(&list[i]) != 0) {
      my_zlog_error("runtime_deps: failed to install %s", list[i].pkg);
      return -1;
    }
  }

  return 0;
}

static gboolean gst_plugin_ready(const char *plugin)
{
  GstRegistry *reg;
  GstPlugin *pl;
  gboolean ok;

  if (!plugin || !plugin[0])
    return TRUE;

  reg = gst_registry_get();
  pl = gst_registry_find_plugin(reg, plugin);
  ok = (pl != NULL);
  if (pl)
    gst_object_unref(pl);
  return ok;
}

static gboolean gst_element_ready(const char *element)
{
  GstElement *el;
  gboolean ok;

  if (!element || !element[0])
    return TRUE;

  el = gst_element_factory_make(element, "runtime-deps-probe");
  if (!el)
    return FALSE;
  ok = (gst_element_set_state(el, GST_STATE_READY) != GST_STATE_CHANGE_FAILURE);
  gst_element_set_state(el, GST_STATE_NULL);
  gst_object_unref(el);
  return ok;
}

static gboolean gst_element_factory_exists(const char *element)
{
  GstElementFactory *f;

  if (!element || !element[0])
    return TRUE;
  f = gst_element_factory_find(element);
  if (!f)
    return FALSE;
  gst_object_unref(f);
  return TRUE;
}

static gboolean gst_h264_encoder_ok(void)
{
  GstElement *enc = gst_element_factory_make("mpph264enc", "runtime-deps-mpp");
  if (enc) {
    GstStateChangeReturn ret = gst_element_set_state(enc, GST_STATE_READY);
    gboolean ok = (ret != GST_STATE_CHANGE_FAILURE);
    gst_element_set_state(enc, GST_STATE_NULL);
    gst_object_unref(enc);
    if (ok) {
      my_zlog_info("runtime_deps: H.264 encoder mpph264enc OK");
      return TRUE;
    }
    my_zlog_warn("runtime_deps: mpph264enc probe failed, checking x264enc");
  }
  if (!gst_element_factory_exists("x264enc")) {
    my_zlog_error("runtime_deps: no mpph264enc or x264enc");
    return FALSE;
  }
  my_zlog_info("runtime_deps: H.264 encoder x264enc available (software fallback)");
  return TRUE;
}

static gboolean gst_audio_capture_ok(void)
{
  if (!gst_plugin_ready("alsa") || !gst_element_factory_exists("alsasrc")) {
    my_zlog_error("runtime_deps: alsasrc missing");
    return FALSE;
  }
  if (gst_plugin_ready("pulseaudio") && gst_element_factory_exists("pulsesrc"))
    my_zlog_info("runtime_deps: mic capture pulsesrc+alsasrc (pulsesrc may need user Pulse at runtime)");
  else
    my_zlog_info("runtime_deps: mic capture alsasrc only (no pulse plugin)");
  return TRUE;
}

static void gst_refresh_registry(void)
{
  /* 勿传 NULL 给 gst_registry_scan_path；apt 装完插件后刷新缓存即可 */
  gst_update_registry();
}

static gboolean gst_check_required_plugins(void)
{
  size_t i;

  for (i = 0; i < sizeof(s_webrtc_gst_required) / sizeof(s_webrtc_gst_required[0]); i++) {
    const GstNeed *g = &s_webrtc_gst_required[i];
    if (!gst_plugin_ready(g->plugin)) {
      my_zlog_warn("runtime_deps: missing GStreamer plugin %s", g->plugin);
      return FALSE;
    }
    if (g->element && !gst_element_factory_exists(g->element)) {
      my_zlog_warn("runtime_deps: missing GStreamer element %s", g->element);
      return FALSE;
    }
    if (g->element && g_strcmp0(g->element, "v4l2src") != 0 &&
        !gst_element_ready(g->element)) {
      my_zlog_warn("runtime_deps: element not ready %s", g->element);
      return FALSE;
    }
  }
  /* 摄像头不在 /dev/video0 时，尝试 USB 重置复位到 video0 */
  {
    gboolean need_reset = TRUE;
    for (int i = 0; i < 3; i++) {
      gchar *path = g_strdup_printf("/dev/video%d", i);
      if (g_file_test(path, G_FILE_TEST_EXISTS) && i == 0) {
        /* video0 存在，检查是否是 capture 设备 */
        gchar *sys_cap = g_strdup_printf("/sys/class/video4linux/video%d/device_caps", i);
        if (g_file_test(sys_cap, G_FILE_TEST_EXISTS)) {
          gchar *contents = NULL;
          if (g_file_get_contents(sys_cap, &contents, NULL, NULL)) {
            guint64 caps = g_ascii_strtoull(g_strstrip(contents), NULL, 0);
            if ((caps & 0x00000001U) || (caps & 0x00001000U))
              need_reset = FALSE;
            g_free(contents);
          }
        }
        g_free(sys_cap);
      }
      g_free(path);
    }
    if (need_reset) {
      my_zlog_warn("runtime_deps: camera not on /dev/video0, trying USB reset");
      /* uvcvideo 模块重载：释放所有 USB 摄像头并重新枚举 */
      int rc1 = system("modprobe -r uvcvideo 2>/dev/null");
      if (rc1 == 0) {
        g_usleep(500000);
        system("modprobe uvcvideo 2>/dev/null");
        g_usleep(2000000); /* 等 2 秒让设备重新枚举 */
        my_zlog_info("runtime_deps: USB camera reset done");
      } else {
        /* modprobe 失败，尝试 usbreset */
        system("for dev in /dev/video*; do usbreset \"$dev\" 2>/dev/null; done");
        g_usleep(1000000);
        my_zlog_info("runtime_deps: usbreset done");
      }
    }
  }
  if (!gst_h264_encoder_ok() || !gst_audio_capture_ok())
    return FALSE;
  return TRUE;
}

static int ensure_gstreamer_plugins(void)
{
  if (!gst_is_initialized()) {
    gst_init(NULL, NULL);
    /* 把 GLib/GObject/GStreamer 警告重定向到 zlog，避免污染终端 stderr */
    g_log_set_default_handler(glib_log_handler, NULL);
  }

  if (gst_check_required_plugins()) {
    my_zlog_info("runtime_deps: GStreamer plugins OK");
    return 0;
  }

  if (ensure_deb_list(s_webrtc_debs,
          sizeof(s_webrtc_debs) / sizeof(s_webrtc_debs[0])) != 0)
    return -1;

  gst_refresh_registry();

  if (gst_check_required_plugins()) {
    my_zlog_info("runtime_deps: GStreamer plugins OK after install");
    return 0;
  }

  my_zlog_error("runtime_deps: GStreamer check failed after install");
  return -1;
}

static gboolean chromium_binary_present(void)
{
  size_t i;
  for (i = 0; i < sizeof(s_chromium_bins) / sizeof(s_chromium_bins[0]); i++) {
    if (access(s_chromium_bins[i], X_OK) == 0)
      return TRUE;
  }
  return FALSE;
}

static int ensure_browser_stack(void)
{
  if (ensure_deb_list(s_browser_debs,
          sizeof(s_browser_debs) / sizeof(s_browser_debs[0])) != 0)
    return -1;

  if (!chromium_binary_present()) {
    my_zlog_error("runtime_deps: chromium binary not found after apt");
    return -1;
  }

  my_zlog_info("runtime_deps: browser stack OK");
  return 0;
}

static int ensure_webrtc_stack(void)
{
  if (access("/dev/mpp_service", R_OK) != 0)
    my_zlog_warn("runtime_deps: /dev/mpp_service missing (kernel VPU driver?)");

  if (ensure_deb_list(s_webrtc_debs,
          sizeof(s_webrtc_debs) / sizeof(s_webrtc_debs[0])) != 0)
    return -1;

  return ensure_gstreamer_plugins();
}

static int ensure_libdatachannel_stack(void)
{
  if (ensure_webrtc_stack() != 0)
    return -1;
  my_zlog_info("runtime_deps: libdatachannel native sender ready (MPP live encode)");
  return 0;
}

int runtime_deps_ensure(void)
{
#if WEBRTCPUSH_USE_MPP
  my_zlog_info("runtime_deps: try libdatachannel WebRTC (WEBRTCPUSH_USE_MPP=1)");
  if (ensure_libdatachannel_stack() == 0) {
    s_webrtc_push_ok = TRUE;
    my_zlog_info("runtime_deps: WebRTC push ready");
    return 0;
  }
  my_zlog_warn("runtime_deps: WebRTC stack failed, will use Chromium browser");
  s_webrtc_push_ok = FALSE;
  if (ensure_browser_stack() != 0)
    my_zlog_warn("runtime_deps: browser stack also incomplete");
  return 0;
#else
  my_zlog_info("runtime_deps: mode=Chromium browser (WEBRTCPUSH_USE_MPP=0)");
  if (ensure_browser_stack() != 0)
    my_zlog_warn("runtime_deps: browser stack incomplete");
  s_webrtc_push_ok = FALSE;
  return 0;
#endif
}

gboolean runtime_deps_use_webrtc_push(void)
{
#if !WEBRTCPUSH_USE_MPP
  return FALSE;
#else
  return s_webrtc_push_ok && !s_webrtc_push_disabled;
#endif
}

void runtime_deps_disable_webrtc_push(void)
{
#if WEBRTCPUSH_USE_MPP
  if (s_webrtc_push_disabled)
    return;
  s_webrtc_push_disabled = TRUE;
  s_webrtc_push_ok = FALSE;
  my_zlog_warn("runtime_deps: disable WebRTC push, fallback to Chromium");
  if (ensure_browser_stack() != 0)
    my_zlog_warn("runtime_deps: browser deps install failed during fallback");
#endif
}
