#include <unistd.h>
#include <signal.h>
#include <execinfo.h>
#include <git2.h>

#define TPEE_IMPL
#include <tpee.h>
#define TPEE_EXT_IMPL
#include <tpee_ext.h>
#define BUF_IMPL
#include <buf.h>
#define ORB_IMPL
#include <orb.h>
#define UTIL_IMPL
#include "util.h" // IWYU pragma: keep

#define SOSO_PORT "SOSO_PORT"

#include "module/page.h"
#include "module/default.h"
#include "module/index.h"
#include "module/git_list.h"
#include "module/git_repo.h"

TPEE_GET("/", index_page) {
  (void) req;
  buf_t out = BUF_NULL;
  orb_ctx_t ctx = orb_ctx_create(&out);
  orb_module_t index_module = orb_module_create(&INDEX_TYPE, NULL);
  orb_module_t page_module = orb_module_create(&PAGE_TYPE, &((page_param_list_t) {
    .title = "Index",
    .module = &index_module,
  }));
  orb_module_run(&page_module, &ctx);
  tpee_resp_t resp = tpee_resp_create(TPEE_STATUS_OK);
  tpee_resp_body(&resp, (char *) out.bytes, out.len);
  buf_free(&out);
  return resp;
}

TPEE_GET("/git", git_list_page) {
  (void) req;
  buf_t out = BUF_NULL;
  orb_ctx_t ctx = orb_ctx_create(&out);
  orb_module_t git_list_module = orb_module_create(&GIT_LIST_TYPE, &((git_list_param_list_t) {
    .path = "/home/git",
  }));
  orb_module_t page_module = orb_module_create(&PAGE_TYPE, &((page_param_list_t) {
    .title = "Index",
    .module = &git_list_module,
  }));
  orb_module_run(&page_module, &ctx);
  tpee_resp_t resp = tpee_resp_create(TPEE_STATUS_OK);
  tpee_resp_body(&resp, (char *) out.bytes, out.len);
  buf_free(&out);
  return resp;
}

TPEE_GET("/git/*", git_repo_page) {
  char *repo_name = tpee_ext_get_path_var(req->url, "/git/{repo_name}", "repo_name");
  if (!repo_name) return tpee_resp_create(TPEE_STATUS_NOT_FOUND);
  char *tab_mode = NULL;
  if (tpee_ext_is_path_match(req->url, "/git/{repo_name}/{tab_mode}")) {
    tab_mode = tpee_ext_get_path_var(req->url, "/git/{repo_name}/{tab_mode}", "tab_mode");
  }
  if (!tab_mode) {
    tab_mode = strdup("log");
  }
  const char *tab_mode_list[] = { "log", "tree", "refs", "commit", "blob", "raw" };
  size_t tab_mode_list_len = sizeof(tab_mode_list) / sizeof(tab_mode_list[0]);
  bool is_tab_mode_valid = false;
  for (size_t i = 0; i < tab_mode_list_len; i++) {
    if (strcmp(tab_mode, tab_mode_list[i]) == 0) {
      is_tab_mode_valid = true;
      break;
    }
  }
  if (!is_tab_mode_valid) {
    return tpee_resp_create(TPEE_STATUS_NOT_FOUND);
  }
  const char *git_base_path = "/home/git/";
  size_t repo_path_len = strlen(git_base_path) + strlen(repo_name) + 1;
  char repo_path[repo_path_len];
  snprintf(repo_path, repo_path_len, "%s%s", git_base_path, repo_name);
  tpee_pairs_t query_list = tpee_req_queries(req);
  buf_t out = BUF_NULL;
  orb_ctx_t ctx = orb_ctx_create(&out);
  orb_module_t git_repo_module = orb_module_create(&GIT_REPO_TYPE, &((git_repo_param_list_t) {
    .repo_name = repo_name,
    .repo_path = repo_path,
    .tab_mode = tab_mode,
    .flag_ref_oid = tpee_pairs_get(&query_list, "ref"),
    .flag_oid = tpee_pairs_get(&query_list, "oid"),
    .flag_path = tpee_pairs_get(&query_list, "path"),
  }));
  orb_module_t page_module = orb_module_create(&PAGE_TYPE, &((page_param_list_t) {
    .title = repo_name,
    .module = &git_repo_module,
  }));
  orb_module_t *module = &page_module;
  if (strcmp(tab_mode, "raw") == 0) {
    module = &git_repo_module;
  }
  orb_module_run(module, &ctx);
  tpee_pairs_free(&query_list);
  if (tab_mode) {
    free(tab_mode);
  }
  free(repo_name);
  if (ctx.rc.is_fatal) {
    tpee_resp_t resp = tpee_resp_create(TPEE_STATUS_NOT_FOUND);
    buf_free(&out);
    return resp;
  }
  tpee_resp_t resp = tpee_resp_create(TPEE_STATUS_OK);
  tpee_resp_body(&resp, (char *) out.bytes, out.len);
  buf_free(&out);
  return resp;
}

// #include "registry.h"

// TPEE_GET("/orb/module/*", orb_module_serve) {
//   char *module_id = tpee_ext_get_path_var(req->url, "/orb/module/{module_id}", "module_id");
//   tpee_resp_t resp = tpee_resp_create(TPEE_STATUS_NOT_FOUND);
//   if (module_id) {
//     const orb_module_type_t *module_type = NULL;
//     for (size_t i = 0; i < REGISTRY_LEN; i++) {
//       const registry_entry_t *entry = &REGISTRY[i];
//       if (strcmp(module_id, entry->id) == 0) {
//         module_type = entry->type;
//         break;
//       }
//     }
//     if (module_type) {
//       buf_t out = BUF_NULL;
//       orb_ctx_t ctx = orb_ctx_create(&out);
//       void *param_list = NULL;
//       tpee_pairs_t query_list = tpee_req_queries(req);
//       if (module_type == &INDEX_TYPE) {
//         param_list = calloc(1, sizeof(index_param_list_t));
//         index_param_list_t *index_param_list = param_list;
//         index_param_list->title = tpee_pairs_get(&query_list, "title");
//       }
//       if (module_type == &ABOUT_TYPE) {
//         param_list = calloc(1, sizeof(about_param_list_t));
//         about_param_list_t *about_param_list = param_list;
//         about_param_list->headline = tpee_pairs_get(&query_list, "headline");
//         about_param_list->subline = tpee_pairs_get(&query_list, "subline");
//       }
//       orb_module_t module = orb_module_create(module_type, param_list);
//       orb_module_run(&module, &ctx);
//       tpee_pairs_free(&query_list);
//       if (param_list) {
//         free(param_list);
//       }
//       resp.status = TPEE_STATUS_OK;
//       tpee_resp_body(&resp, (char *) out.bytes, out.len);
//       buf_free(&out);
//     }
//     free(module_id);
//   }
//   return resp;
// }

TPEE_GET("/asset/*", asset_serve) {
  const char *prefix = "/asset/";
  const char *filepath = "./asset/";
  size_t filename_len = strlen(filepath) + strlen(req->url) - strlen(prefix) + 1;
  char filename[filename_len];
  snprintf(filename, filename_len, "%s%s", filepath, req->url + strlen(prefix));
  tpee_resp_t resp = tpee_ext_serve_file(filename);
  return resp;
}

TPEE_DEFAULT(default_page) {
  (void) req;
  buf_t out = BUF_NULL;
  orb_ctx_t ctx = orb_ctx_create(&out);
  orb_module_t default_module = orb_module_create(&DEFAULT_TYPE, NULL);
  orb_module_t page_module = orb_module_create(&PAGE_TYPE, &((page_param_list_t) {
    .title = "404",
    .module = &default_module,
  }));
  orb_module_run(&page_module, &ctx);
  tpee_resp_t resp = tpee_resp_create(TPEE_STATUS_OK);
  tpee_resp_body(&resp, (char *) out.bytes, out.len);
  buf_free(&out);
  return resp;
}

void on_signal(int signal) {
  (void) signal;
  // do nothing
}

void on_segfault(int signal) {
  void *list[10];
  size_t size = backtrace(list, 10);
  fprintf(stderr, "ERROR: signal %d:\n", signal);
  backtrace_symbols_fd(list, size, STDOUT_FILENO);
  exit(1);
}

int main(void) {
  struct sigaction action;
  action.sa_handler = on_signal;
  action.sa_flags = 0;
  sigemptyset(&action.sa_mask);
  sigaction(SIGINT, &action, NULL);
  struct sigaction sigint_action;
  sigint_action.sa_handler = on_segfault;
  sigint_action.sa_flags = 0;
  sigemptyset(&sigint_action.sa_mask);
  sigaction(SIGSEGV, &sigint_action, NULL);
  char *port_str = getenv(SOSO_PORT);
  if (!port_str) {
    printf("no port specified! set environment variable SOSO_PORT.\n");
    return 1;
  }
  char *end = NULL;
  int port = strtoimax(port_str, &end, 10);
  if (end == port_str || *end != '\0') {
    printf("invalid port specified! set environment variable SOSO_PORT.\n");
    return 1;
  }
  tpee_t *tpee = tpee_init();
  if (tpee_bind(tpee, "0.0.0.0", port, NULL, 0) != TPEE_ERR_OK) {
    printf("failed to bind port!\n");
    return 1;
  }
  git_libgit2_init();
  git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 0);
  if (tpee_listen(tpee) == TPEE_ERR_OK) {
    printf("ready and listening (pid: %d, port: %d)\n", getpid(), port);
    pause();
  }
  git_libgit2_shutdown();
  printf("stopped\n");
  tpee_close(tpee);
  tpee_cleanup(tpee);
  return 0;
}
