#define SEOBEO_ENABLE_DEBUG
#include "seobeo/seobeo.h"
#include <time.h>

// UUID + /tmp/ + format (max 4)
#define TMP_FILE_LENGTH 47
#define UUID_LEN 37

volatile sig_atomic_t stop_server = 0;
static _Atomic uint32_t counter = 0;

void handle_sigint(int sig)
{
  printf("Failed\n");
  stop_server = 1;
}

void Seobeo_ServerSideRender(
    char *final_body,
    char *path,
    Dowa_Arena *arena
) {
  Seobeo_Log(SEOBEO_DEBUG, "[Curr] %s\n", path);
  size_t html_size = 0;
  char *template = Seobeo_Web_LoadFile(path, &html_size);
  if (!template) return;
  Seobeo_Log(SEOBEO_DEBUG, "[Curr] ??\n");

  size_t current_offset = 0;
  char *cursor = template;

  int32 token_len = 2;

  while (1)
  {
    char *start_tag = strstr(cursor, "{{");
    if (!start_tag) break;

    char *end_tag = strstr(start_tag, "}}");
    if (!end_tag) break;

    size_t leading_len = start_tag - cursor;
    memcpy(final_body + current_offset, cursor, leading_len);
    current_offset += leading_len;

    size_t name_len = end_tag - (start_tag + token_len);
    char *include_name = Dowa_Arena_Allocate(arena, name_len + 1);
    memcpy(include_name, start_tag + token_len, name_len);
    include_name[name_len] = '\0';

    size_t sub_file_size = 0;
    char *sub_content = Seobeo_Web_LoadFile(include_name, &sub_file_size);
    Seobeo_Log(SEOBEO_DEBUG, "[Curr] Sub content: %s\n", sub_content);
    if (sub_content)
    {
      memcpy(final_body + current_offset, sub_content, sub_file_size);
      current_offset += sub_file_size;
      free(sub_content);
    }

    cursor = end_tag + 2;
  }
  strcpy(final_body + current_offset, cursor);
  free(template);
}

Seobeo_Request_Entry* GetHomePage(Seobeo_Request_Entry *req, Dowa_Arena *arena)
{
  Seobeo_Request_Entry *resp = NULL; 
  char *final_body = Dowa_Arena_Allocate(arena, 50 * 1024);
  Seobeo_ServerSideRender(final_body, "/index.html", arena);
  Dowa_HashMap_Push_Arena(resp, "body", final_body, arena);
  return resp;
}

Seobeo_Request_Entry* GetResume(Seobeo_Request_Entry *req, Dowa_Arena *arena)
{
  Seobeo_Request_Entry *resp = NULL; 
  char *final_body = Dowa_Arena_Allocate(arena, 50 * 1024);
  Seobeo_ServerSideRender(final_body, "/resume/index.html", arena);
  Dowa_HashMap_Push_Arena(resp, "body", final_body, arena);
  return resp;
}

Seobeo_Request_Entry* GetTools(Seobeo_Request_Entry *req, Dowa_Arena *arena)
{
  Seobeo_Request_Entry *resp = NULL; 
  char *final_body = Dowa_Arena_Allocate(arena, 50 * 1024);
  Seobeo_ServerSideRender(final_body, "/tools/index.html", arena);
  Dowa_HashMap_Push_Arena(resp, "body", final_body, arena);
  return resp;
}


Seobeo_Request_Entry* GetMDToHTML(Seobeo_Request_Entry *req, Dowa_Arena *arena)
{
  Seobeo_Request_Entry *resp = NULL;
  char *final_body = Dowa_Arena_Allocate(arena, 50 * 1024);
  Seobeo_ServerSideRender(final_body, "/tools/markdown_to_html/index.html", arena);
  Dowa_HashMap_Push_Arena(resp, "body", final_body, arena);
  return resp;
}

Seobeo_Request_Entry* GetFileConverter(Seobeo_Request_Entry *req, Dowa_Arena *arena)
{
  Seobeo_Request_Entry *resp = NULL;
  char *final_body = Dowa_Arena_Allocate(arena, 50 * 1024);
  Seobeo_ServerSideRender(final_body, "/tools/file_converter/index.html", arena);
  Dowa_HashMap_Push_Arena(resp, "body", final_body, arena);
  return resp;
}

Seobeo_Request_Entry *ConvertImageToWebP(Seobeo_Request_Entry *req, Dowa_Arena *arena)
{
  Seobeo_Request_Entry *resp = NULL;

  if (!req)
  {
    printf("ERROR: Request is NULL\n");
    char *error_msg = "Internal error: no request data";
    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
    return resp;
  }

  size_t req_length = Dowa_Array_Length(req);
  printf("Request has %zu entries\n", req_length);

  for (size_t i = 0; i < req_length; i++)
  {
    printf("  Key[%zu]: '%s'\n", i, req[i].key);
  }

  void *body_kv = Dowa_HashMap_Get_Ptr(req, "Body");
  if (!body_kv)
  {
    printf("ERROR: No 'Body' key found in request\n");

    char *error_msg = "No file data provided";
    Dowa_HashMap_Push_Arena(resp, "status", "400", arena);
    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
    return resp;
  }

  void *cl_kv = Dowa_HashMap_Get_Ptr(req, "Content-Length");
  if (!cl_kv)
  {
    char *error_msg = "No Content-Length header";
    Dowa_HashMap_Push_Arena(resp, "status", "400", arena);
    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
    return resp;
  }

  const char *file_data = ((Seobeo_Request_Entry*)body_kv)->value;
  const char *content_length_str = ((Seobeo_Request_Entry*)cl_kv)->value;
  size_t file_size = atoi(content_length_str);

  printf("DEBUG: Converting image, file_size=%zu bytes\n", file_size);

   
  int open_flags = O_RDWR | O_CREAT | O_EXCL;

  char *uuid4 = (char *)Dowa_Arena_Allocate(arena, UUID_LEN);
  uint32 seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++;
  Dowa_String_UUID(seed, uuid4);
  char *input_path = Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);;
  snprintf(input_path, TMP_FILE_LENGTH, "/tmp/%s", uuid4);
  int input_fd = open(input_path, open_flags, 0600);
  if (input_fd == -1)
  {
    char *error_msg = "Failed to create temporary file";
    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
    return resp;
  }
  write(input_fd, file_data, file_size);
  close(input_fd);


  uuid4 = (char *)Dowa_Arena_Allocate(arena, UUID_LEN);
  seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++;
  Dowa_String_UUID(seed, uuid4);
  char *output_path = (char *)Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);;
  snprintf(output_path, TMP_FILE_LENGTH, "/tmp/%s.webp", uuid4);
  printf("[DEBUG] output_path %s\n", output_path);
  printf("[DEBUG] open_flags: 0x%x\n", open_flags);
  printf("[DEBUG] input_path: %s\n", input_path);
  int output_fd = open(output_path, open_flags, 0600);
  printf("[DEBUG] output_fd: %d\n", output_fd);
  if (output_fd == -1)
  {
    unlink(input_path);
    printf("[DEBUG] errno: %d (%s)\n", errno, strerror(errno));
    char *error_msg = "Failed to create output file";
    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
    return resp;
  }
  close(output_fd);

  char cmd[1024];
  snprintf(cmd, sizeof(cmd), "ffmpeg -y -i %s -quality 80 %s 2>/tmp/error_log",
           input_path, output_path);
  int result = system(cmd);
  if (result != 0)
  {
    unlink(input_path);
    unlink(output_path);
    char *error_msg = "FFmpeg conversion failed";
    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
    return resp;
  }

  size_t converted_size = 0;
  FILE *out_file = fopen(output_path, "rb");
  if (!out_file)
  {
    unlink(input_path);
    unlink(output_path);
    char *error_msg = "Failed to read converted file";
    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
    return resp;
  }
  fclose(out_file);

  unlink(input_path);

  char *filename = strrchr(output_path, '/') + 1;
  char *response_body = Dowa_Arena_Allocate(arena, 512);
  snprintf(response_body, 512,
           "{\"success\":true,\"download_url\":\"/api/download/%s\",\"expires\":\"10 minutes\"}",
           filename);
  Dowa_HashMap_Push_Arena(resp, "status", "200", arena);
  Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
  Dowa_HashMap_Push_Arena(resp, "body", response_body, arena);

  printf("DEBUG: Image converted, available at /api/download/%s\n", filename);

  return resp;
}

Seobeo_Request_Entry *ConvertVideoToMP4(Seobeo_Request_Entry *req, Dowa_Arena *arena)
{
  Seobeo_Request_Entry *resp = NULL;

  void *body_kv = Dowa_HashMap_Get_Ptr(req, "Body");
  if (!body_kv)
  {
    char *error_msg = "No file data provided";
    Dowa_HashMap_Push_Arena(resp, "status", "400", arena);
    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
    return resp;
  }

  // Get Content-Length to know the actual binary size
  void *cl_kv = Dowa_HashMap_Get_Ptr(req, "Content-Length");
  if (!cl_kv)
  {
    char *error_msg = "No Content-Length header";
    Dowa_HashMap_Push_Arena(resp, "status", "400", arena);
    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
    return resp;
  }

  const char *file_data = ((Seobeo_Request_Entry*)body_kv)->value;
  const char *content_length_str = ((Seobeo_Request_Entry*)cl_kv)->value;
  size_t file_size = atoi(content_length_str);

  printf("DEBUG: Converting video, file_size=%zu bytes\n", file_size);

  char *uuid4 = (char *)Dowa_Arena_Allocate(arena, UUID_LEN);
  Dowa_String_UUID((uint32)time(NULL), uuid4);
  char *input_path = Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);
  snprintf(input_path, TMP_FILE_LENGTH, "/tmp/%s", uuid4);
  int input_fd = mkstemp(input_path);
  if (input_fd == -1)
  {
    char *error_msg = "Failed to create temporary file";
    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
    return resp;
  }

  write(input_fd, file_data, file_size);
  close(input_fd);

  int open_flags = O_RDWR | O_CREAT | O_EXCL;

  uint32 seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++;;
  Dowa_String_UUID(seed, uuid4);
  char *output_path = (char *)Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);;
  snprintf(output_path, TMP_FILE_LENGTH, "/tmp/%s.mp4", uuid4);
  int output_fd = open(output_path, open_flags, 0600);
  if (output_fd == -1)
  {
    unlink(input_path);
    char *error_msg = "Failed to create output file";
    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
    return resp;
  }
  close(output_fd);

  char cmd[512];
  snprintf(cmd, sizeof(cmd),
           "ffmpeg -y -i %s -c:v libx264 -preset fast -crf 23 -c:a aac %s 2>/tmp/error_log",
           input_path, output_path);
  int result = system(cmd);
  if (result != 0)
  {
    unlink(input_path);
    unlink(output_path);
    char *error_msg = "FFmpeg conversion failed";
    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
    return resp;
  }

  unlink(input_path);
  char *filename = strrchr(output_path, '/') + 1;
  char *response_body = Dowa_Arena_Allocate(arena, 512);
  snprintf(response_body, 512,
           "{\"success\":true,\"download_url\":\"/api/download/%s\",\"expires\":\"10 minutes\"}",
           filename);
  Dowa_HashMap_Push_Arena(resp, "status", "200", arena);
  Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
  Dowa_HashMap_Push_Arena(resp, "body", response_body, arena);

  printf("DEBUG: Video converted, available at /api/download/%s\n", filename);
  return resp;
}

Seobeo_Request_Entry *DownloadConvertedFile(Seobeo_Request_Entry *req, Dowa_Arena *arena)
{
  Seobeo_Request_Entry *resp = NULL;

  void *filename_kv = Dowa_HashMap_Get_Ptr(req, ":filename");
  if (!filename_kv)
  {
    char *error_msg = "No filename specified";
    Dowa_HashMap_Push_Arena(resp, "status", "404", arena);
    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
    return resp;
  }

  const char *filename = ((Seobeo_Request_Entry*)filename_kv)->value;

  // TODO: Maybe check if the uuid is allowed or not?
  // if (strlen(filename) != TMP_FILE_LENGTH)
  // {
  //   char *error_msg = "Not Allowed Filename";
  //   Dowa_HashMap_Push_Arena(resp, "status", "404", arena);
  //   Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
  //   Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
  //   return resp;
  // }
  // boolean allowed = FALSE;
  // for (int i = 0; i < Dowa_Array_Length(g_uuid4_array); i++)
  // {
  //   if strcmp(g_uuid4_array, filename)
  //   {
  //     allowed = TRUE;
  //     break;
  //   }
  //   g_uuid4_array++;
  // }

  char filepath[512];
  snprintf(filepath, sizeof(filepath), "/tmp/%s", filename);

  FILE *file = fopen(filepath, "rb");
  if (!file)
  {
    char *error_msg = "File not found or expired";
    Dowa_HashMap_Push_Arena(resp, "status", "404", arena);
    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
    return resp;
  }

  fseek(file, 0, SEEK_END);
  size_t file_size = ftell(file);
  fseek(file, 0, SEEK_SET);

  char *file_data = malloc(file_size + 1);
  if (!file_data)
  {
    fclose(file);
    char *error_msg = "Memory allocation failed";
    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
    return resp;
  }

  fread(file_data, 1, file_size, file);
  file_data[file_size] = '\0';
  fclose(file);

  const char *content_type = "application/octet-stream";
  if (strstr(filename, ".webp"))
    content_type = "image/webp";
  else if (strstr(filename, ".mp4"))
    content_type = "video/mp4";

  char *body = Dowa_Arena_Allocate(arena, file_size + 1);
  memcpy(body, file_data, file_size);
  body[file_size] = '\0';
  free(file_data);

  unlink(filepath);

  printf("DEBUG: Served and deleted file: %s (%zu bytes)\n", filename, file_size);

  // Set proper Content-Length for binary data
  char *content_length = Dowa_Arena_Allocate(arena, 32);
  snprintf(content_length, 32, "%zu", file_size);

  Dowa_HashMap_Push_Arena(resp, "status", "200", arena);
  Dowa_HashMap_Push_Arena(resp, "content-type", content_type, arena);
  Dowa_HashMap_Push_Arena(resp, "content-length", content_length, arena);
  Dowa_HashMap_Push_Arena(resp, "body", body, arena);

  return resp;
}

Seobeo_Request_Entry *RenderBlogList(Seobeo_Request_Entry *req, Dowa_Arena *arena)
{
  Seobeo_Request_Entry *resp = NULL;
  char *final_body = Dowa_Arena_Allocate(arena, 50 * 1024);
  Seobeo_ServerSideRender(final_body, "/blog/index.html", arena);
  Dowa_HashMap_Push_Arena(resp, "body", final_body, arena);
  return resp;
}


Seobeo_Request_Entry *RenderBlog(Seobeo_Request_Entry *req, Dowa_Arena *arena)
{
  Seobeo_Log(SEOBEO_DEBUG, "[CURR], Hello\n");
  Seobeo_Request_Entry *resp = NULL;

  void *blog_id_kv = Dowa_HashMap_Get_Ptr(req, ":blog_id");
  if (!blog_id_kv)
  {
    char *error_msg = "No Blog Id";
    Dowa_HashMap_Push_Arena(resp, "status", "404", arena);
    Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
    Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena);
    return resp;
  }

  const char *blog_id = ((Seobeo_Request_Entry *)blog_id_kv)->value;
  char *html_path = Dowa_Arena_Allocate(arena, 512);
  sprintf(html_path, "/blog/%s/index.html", blog_id);

  char *final_body = Dowa_Arena_Allocate(arena, 100 * 1024); // TODO: Think about the sizes
  Seobeo_ServerSideRender(final_body, html_path, arena);
  Dowa_HashMap_Push_Arena(resp, "body", final_body, arena);
  return resp;
}


CREATE_REDIRECT_HANDLER(HomePage, "/")
CREATE_REDIRECT_HANDLER(Resume, "/resume")
CREATE_REDIRECT_HANDLER(Tools, "/tools")
CREATE_REDIRECT_HANDLER(MarkDownToHtml, "/tools/markdown_to_html")
CREATE_REDIRECT_HANDLER(FileConverter, "/tools/file_converter")

int main(void)
{
  Seobeo_Router_Init();
  Seobeo_Router_Register("GET", "/", GetHomePage);
  Seobeo_Router_Register("GET", "/index.html", GetRedirectHomePage);

  Seobeo_Router_Register("GET", "/resume", GetResume);
  Seobeo_Router_Register("GET", "/resume/index.html", GetRedirectResume);

  Seobeo_Router_Register("GET", "/tools", GetTools);
  Seobeo_Router_Register("GET", "/tools/index.html", GetRedirectTools);

  Seobeo_Router_Register("GET", "/tools/markdown_to_html", GetMDToHTML);
  Seobeo_Router_Register("GET", "/tools/markdown_to_html/index.html", GetRedirectMarkDownToHtml);

  Seobeo_Router_Register("GET", "/tools/file_converter", GetFileConverter);
  Seobeo_Router_Register("GET", "/tools/file_converter/index.html", GetRedirectFileConverter);

  // -- File converter --/
  Seobeo_Router_Register("POST", "/api/convert/image-to-webp", ConvertImageToWebP);
  Seobeo_Router_Register("POST", "/api/convert/video-to-mp4", ConvertVideoToMP4);
  Seobeo_Router_Register("GET", "/api/download/:filename", DownloadConvertedFile);

  // -- Blog --/
  Seobeo_Router_Register("GET", "/blog", RenderBlogList);
  Seobeo_Router_Register("GET", "/blog/:blog_id", RenderBlog);

  Seobeo_Web_Server_Start("mrjunejune/src", "6969", SEOBEO_MODE_EDGE, 3);
}
