#define _GNU_SOURCE

#include <arpa/inet.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

#ifndef AGENT_HOST
#define AGENT_HOST "192.168.40.252"
#endif
#ifndef AGENT_PORT
#define AGENT_PORT 9000
#endif
#ifndef AGENT_TOKEN
#define AGENT_TOKEN "kaios-local-relay"
#endif

#define OUT_MAX 32768
#define ARG_MAX_LEN 4096
#define SHELL_TIMEOUT_SECONDS 60

static char g_client[96];
static char g_out[OUT_MAX];
static size_t g_len;

static void out_reset(void) {
  g_len = 0;
  g_out[0] = 0;
}

static void out(const char *fmt, ...) {
  va_list ap;
  int n;
  if (g_len >= sizeof(g_out) - 1) return;
  va_start(ap, fmt);
  n = vsnprintf(g_out + g_len, sizeof(g_out) - g_len, fmt, ap);
  va_end(ap);
  if (n <= 0) return;
  g_len += (size_t)n;
  if (g_len >= sizeof(g_out)) g_len = sizeof(g_out) - 1;
  g_out[g_len] = 0;
}

static int connect_server(void) {
  int s;
  struct sockaddr_in addr;
  s = socket(AF_INET, SOCK_STREAM, 0);
  if (s < 0) return -1;
  memset(&addr, 0, sizeof(addr));
  addr.sin_family = AF_INET;
  addr.sin_port = htons(AGENT_PORT);
  if (inet_pton(AF_INET, AGENT_HOST, &addr.sin_addr) != 1) {
    close(s);
    return -1;
  }
  if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
    close(s);
    return -1;
  }
  return s;
}

static int http_exchange(const char *req, const char *body, char *resp, size_t resp_sz) {
  int s = connect_server();
  size_t off = 0;
  ssize_t n;
  if (s < 0) return -1;
  if (write(s, req, strlen(req)) < 0) {
    close(s);
    return -1;
  }
  if (body && body[0] && write(s, body, strlen(body)) < 0) {
    close(s);
    return -1;
  }
  while (off + 1 < resp_sz && (n = read(s, resp + off, resp_sz - off - 1)) > 0) {
    off += (size_t)n;
  }
  close(s);
  resp[off] = 0;
  return 0;
}

static const char *http_body(char *resp) {
  char *p = strstr(resp, "\r\n\r\n");
  return p ? p + 4 : resp;
}

static void dump_file(const char *label, const char *path, size_t max) {
  int fd = open(path, O_RDONLY | O_CLOEXEC);
  char buf[512];
  ssize_t n;
  size_t total = 0;
  out("\n=== %s: %s ===\n", label, path);
  if (fd < 0) {
    out("open failed: %s\n", strerror(errno));
    return;
  }
  while ((n = read(fd, buf, sizeof(buf))) > 0 && total < max) {
    size_t want = (size_t)n;
    if (total + want > max) want = max - total;
    if (g_len + want + 1 >= sizeof(g_out)) break;
    memcpy(g_out + g_len, buf, want);
    g_len += want;
    g_out[g_len] = 0;
    total += want;
  }
  close(fd);
  out("\n");
}

static void stat_one(const char *path) {
  struct stat st;
  if (lstat(path, &st) != 0) {
    out("%s: lstat failed: %s\n", path, strerror(errno));
    return;
  }
  out("%s: mode=0%o uid=%ld gid=%ld size=%lld\n",
      path, st.st_mode & 07777, (long)st.st_uid, (long)st.st_gid, (long long)st.st_size);
}

static void list_dir(const char *path) {
  DIR *d;
  struct dirent *e;
  int count = 0;
  out("\n=== list %s ===\n", path);
  d = opendir(path);
  if (!d) {
    out("opendir failed: %s\n", strerror(errno));
    return;
  }
  while ((e = readdir(d)) && count++ < 80) {
    out("%s\n", e->d_name);
  }
  closedir(d);
}

static void action_identity(void) {
  struct utsname uts;
  out("client=%s\n", g_client);
  out("uid=%ld euid=%ld gid=%ld egid=%ld pid=%ld ppid=%ld\n",
      (long)getuid(), (long)geteuid(), (long)getgid(), (long)getegid(),
      (long)getpid(), (long)getppid());
  if (uname(&uts) == 0) out("uname=%s %s %s %s\n", uts.sysname, uts.release, uts.version, uts.machine);
  dump_file("selinux", "/proc/self/attr/current", 4096);
  dump_file("status", "/proc/self/status", 12000);
  dump_file("mounts", "/proc/mounts", 12000);
}

static void action_service_tree(void) {
  stat_one("/data/local/service");
  stat_one("/data/local/service/api-daemon");
  stat_one("/data/local/service/api-daemon/api-daemon");
  stat_one("/data/local/service/api-daemon/config.toml");
  stat_one("/data/local/service/api-daemon/remote_services.toml");
  stat_one("/data/local/service/updater");
  stat_one("/data/local/service/updater/updater-daemon");
  stat_one("/data/local/service/updater/updater-daemon.orig_codex");
  list_dir("/data/local/service/api-daemon");
  list_dir("/data/local/service/updater");
}

static void action_remote_tree(void) {
  stat_one("/data/local/service/api-daemon/remote");
  stat_one("/data/local/service/api-daemon/remote_services.toml");
  list_dir("/data/local/service/api-daemon/remote");
  dump_file("remote_services.toml", "/data/local/service/api-daemon/remote_services.toml", 8192);
}

static void action_shell(const char *command) {
  int pipefd[2];
  pid_t pid;
  char buf[512];
  ssize_t n;
  int status = 0;
  int child_done = 0;
  int timed_out = 0;
  time_t deadline;

  out("command=%s\n", command ? command : "");
  if (!command || !command[0]) {
    out("empty command\n");
    return;
  }
  if (pipe(pipefd) != 0) {
    out("pipe failed: %s\n", strerror(errno));
    return;
  }

  pid = fork();
  if (pid < 0) {
    out("fork failed: %s\n", strerror(errno));
    close(pipefd[0]);
    close(pipefd[1]);
    return;
  }

  if (pid == 0) {
    close(pipefd[0]);
    dup2(pipefd[1], STDOUT_FILENO);
    dup2(pipefd[1], STDERR_FILENO);
    close(pipefd[1]);
    execl("/system/bin/sh", "sh", "-c", command, (char *)NULL);
    dprintf(STDERR_FILENO, "exec failed: %s\n", strerror(errno));
    _exit(127);
  }

  close(pipefd[1]);
  fcntl(pipefd[0], F_SETFL, fcntl(pipefd[0], F_GETFL, 0) | O_NONBLOCK);
  deadline = time(NULL) + SHELL_TIMEOUT_SECONDS;

  while (!child_done) {
    while ((n = read(pipefd[0], buf, sizeof(buf))) > 0) {
      size_t want = (size_t)n;
      if (g_len + want + 1 >= sizeof(g_out)) {
        want = sizeof(g_out) - g_len - 1;
      }
      if (want == 0) {
        break;
      }
      memcpy(g_out + g_len, buf, want);
      g_len += want;
      g_out[g_len] = 0;
      if (g_len + 1 >= sizeof(g_out)) {
        break;
      }
    }

    if (waitpid(pid, &status, WNOHANG) == pid) {
      child_done = 1;
      break;
    }

    if (time(NULL) >= deadline) {
      timed_out = 1;
      kill(pid, SIGKILL);
      waitpid(pid, &status, 0);
      child_done = 1;
      break;
    }

    usleep(100000);
  }

  while ((n = read(pipefd[0], buf, sizeof(buf))) > 0) {
    size_t want = (size_t)n;
    if (g_len + want + 1 >= sizeof(g_out)) {
      want = sizeof(g_out) - g_len - 1;
    }
    if (want == 0) {
      break;
    }
    memcpy(g_out + g_len, buf, want);
    g_len += want;
    g_out[g_len] = 0;
    if (g_len + 1 >= sizeof(g_out)) {
      break;
    }
  }
  close(pipefd[0]);

  if (timed_out) {
    out("\ntimeout=%d\n", SHELL_TIMEOUT_SECONDS);
  } else if (WIFEXITED(status)) {
    out("\nrc=%d\n", WEXITSTATUS(status));
  } else if (WIFSIGNALED(status)) {
    out("\nsignal=%d\n", WTERMSIG(status));
  } else {
    out("\nstatus=0x%x\n", status);
  }
}

static void post_result(const char *id, const char *action) {
  char req[1024];
  char resp[2048];
  snprintf(req, sizeof(req),
           "POST /agent/result?token=%s&client=%s&id=%s&action=%s HTTP/1.1\r\n"
           "Host: " AGENT_HOST ":%d\r\n"
           "Content-Type: text/plain\r\n"
           "Content-Length: %lu\r\n"
           "Connection: close\r\n\r\n",
           AGENT_TOKEN, g_client, id, action, AGENT_PORT, (unsigned long)strlen(g_out));
  http_exchange(req, g_out, resp, sizeof(resp));
}

static int parse_value(const char *body, const char *key, char *outbuf, size_t outsz) {
  const char *p = strstr(body, key);
  const char *e;
  size_t n;
  if (!p) return -1;
  p += strlen(key);
  e = strchr(p, '\n');
  if (!e) e = p + strlen(p);
  n = (size_t)(e - p);
  if (n >= outsz) n = outsz - 1;
  memcpy(outbuf, p, n);
  outbuf[n] = 0;
  return 0;
}

int main(void) {
  char req[512];
  char resp[8192];
  char id[64], action[64], arg[ARG_MAX_LEN];

  signal(SIGPIPE, SIG_IGN);
  snprintf(g_client, sizeof(g_client), "api-agent-%ld", (long)getpid());

  for (;;) {
    snprintf(req, sizeof(req),
             "GET /agent/wait?token=%s&client=%s&timeout=55 HTTP/1.1\r\n"
             "Host: " AGENT_HOST ":%d\r\n"
             "Connection: close\r\n\r\n",
             AGENT_TOKEN, g_client, AGENT_PORT);
    if (http_exchange(req, "", resp, sizeof(resp)) != 0) {
      sleep(5);
      continue;
    }

    const char *body = http_body(resp);
    if (!strncmp(body, "NONE", 4)) continue;
    if (parse_value(body, "ID=", id, sizeof(id)) != 0 ||
        parse_value(body, "ACTION=", action, sizeof(action)) != 0) {
      sleep(2);
      continue;
    }
    parse_value(body, "ARG=", arg, sizeof(arg));

    out_reset();
    out("=== api_agent result ===\n");
    out("id=%s action=%s arg=%s\n", id, action, arg);

    if (!strcmp(action, "identity")) action_identity();
    else if (!strcmp(action, "service_tree")) action_service_tree();
    else if (!strcmp(action, "remote_tree")) action_remote_tree();
    else if (!strcmp(action, "shell")) action_shell(arg);
    else if (!strcmp(action, "restore_updater")) {
      out("restore_updater is intentionally not automatic while this process is running.\n");
      out("Use relay to stop updater-daemon, copy updater-daemon.orig_codex back, chmod 755, then start updater-daemon.\n");
    } else if (!strcmp(action, "stop")) {
      out("stopping api_agent pid=%ld\n", (long)getpid());
      post_result(id, action);
      return 0;
    } else {
      out("unknown fixed action\n");
    }
    post_result(id, action);
  }
}
