#include "seobeo/seobeo.h"
#include "markdown_converter/markdown_to_html.h"
#include "s3/s3_uploader.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;

// Server configuration (loaded from .config)
static char g_upload_auth_token[256] = {0};
static char g_s3_region[64] = "us-west-2";
static char g_s3_bucket[128] = "mrjunejune";
static int  g_s3_url_expires = 3600;
static S3_Config g_s3_config = {0};

static void load_config(const char *config_path)
{
  FILE *f = fopen(config_path, "r");
  if (!f)
  {
    printf("[CONFIG] Warning: Could not open %s, using defaults\n", config_path);
    return;
  }

  char line[512];
  while (fgets(line, sizeof(line), f))
  {
    // Skip comments and empty lines
    if (line[0] == '#' || line[0] == '\n' || line[0] == '\r') continue;

    char *eq = strchr(line, '=');
    if (!eq) continue;

    *eq = '\0';
    char *key = line;
    char *value = eq + 1;

    // Trim newline from value
    size_t vlen = strlen(value);
    while (vlen > 0 && (value[vlen-1] == '\n' || value[vlen-1] == '\r'))
      value[--vlen] = '\0';

    if (strcmp(key, "UPLOAD_AUTH_TOKEN") == 0)
    {
      strncpy(g_upload_auth_token, value, sizeof(g_upload_auth_token) - 1);
    }
    else if (strcmp(key, "S3_REGION") == 0)
    {
      strncpy(g_s3_region, value, sizeof(g_s3_region) - 1);
    }
    else if (strcmp(key, "S3_BUCKET") == 0)
    {
      strncpy(g_s3_bucket, value, sizeof(g_s3_bucket) - 1);
    }
    else if (strcmp(key, "S3_URL_EXPIRES") == 0)
    {
      g_s3_url_expires = atoi(value);
    }
  }
  fclose(f);

  printf("[CONFIG] Loaded: token=%s..., region=%s, bucket=%s, expires=%d\n",
         g_upload_auth_token[0] ? "***" : "(empty)",
         g_s3_region, g_s3_bucket, g_s3_url_expires);
}

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

void Seobeo_Render_Html(
    char *final_body,
    char *template,
    Dowa_Arena *arena
)
{
  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);
}


void Seobeo_Render_Html_FilePath(
    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_Render_Html(final_body, template, arena);
}

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_Render_Html_FilePath(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_Render_Html_FilePath(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_Render_Html_FilePath(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_Render_Html_FilePath(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_Render_Html_FilePath(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);

  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);
  Seobeo_Log(SEOBEO_DEBUG, "Input path: %s\n", input_path);

  int input_fd = open(input_path, open_flags, 0600);
  if (input_fd == -1)
  {
    Seobeo_Log(SEOBEO_DEBUG, "errno: %d (%s)\n", errno, strerror(errno));
    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);

  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);

  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_Render_Html_FilePath(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_Request_Entry *resp = NULL;

  char *file_path = Dowa_Arena_Allocate(arena, 1024);
  void *blog_id_kv = Dowa_HashMap_Get_Ptr(req, ":blog_id");
  char *blog_id = ((Seobeo_Request_Entry*)blog_id_kv)->value;
  snprintf(file_path, 1024, "/blog/%s/index.html", blog_id);

  char *final_body = Dowa_Arena_Allocate(arena, 50 * 1024);
  Seobeo_Render_Html_FilePath(final_body, file_path, arena);
  Dowa_HashMap_Push_Arena(resp, "body", final_body, arena);
  return resp;
}

void Chat_Handler(Seobeo_WebSocket_Server_Connection *p_conn, Seobeo_WebSocket_Message *p_msg, void *p_user_data)
{
  (void)p_user_data;

  if (p_msg->opcode == SEOBEO_WS_OPCODE_TEXT)
  {
    char message[2048];
    snprintf(message, sizeof(message), "[%s]: %.*s", p_conn->client_id, (int)p_msg->length, (char*)p_msg->data);

    Seobeo_Log(SEOBEO_INFO, "[Chat] Broadcasting: %s\n", message);
    Seobeo_WebSocket_Server_Broadcast_Text(message, p_conn);
  }
}

Seobeo_Request_Entry *GetTalk(Seobeo_Request_Entry *req, Dowa_Arena *arena)
{
  Seobeo_Request_Entry *resp = NULL;
  char *final_body = Dowa_Arena_Allocate(arena, 50 * 1024);
  Seobeo_Render_Html_FilePath(final_body, "/talk/index.html", 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")
CREATE_REDIRECT_HANDLER(Talk, "/talk")

// S3 Upload URL API
// POST /api/s3/upload-url
// Headers: Authorization: Bearer <token>, Content-Type: application/json
// Body: {"filename": "photo.png", "content_type": "image/png"}
// Returns: {"upload_url": "https://...", "key": "uploads/..."}
Seobeo_Request_Entry *GetS3UploadUrl(Seobeo_Request_Entry *req, Dowa_Arena *arena)
{
  Seobeo_Request_Entry *resp = NULL;

  // Check auth token
  void *auth_kv = Dowa_HashMap_Get_Ptr(req, "Authorization");
  if (!auth_kv)
  {
    Dowa_HashMap_Push_Arena(resp, "status", "401", arena);
    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Missing Authorization header\"}", arena);
    return resp;
  }

  const char *auth_header = ((Seobeo_Request_Entry*)auth_kv)->value;

  // Expect "Bearer <token>"
  if (strncmp(auth_header, "Bearer ", 7) != 0)
  {
    Dowa_HashMap_Push_Arena(resp, "status", "401", arena);
    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Invalid Authorization format, use Bearer token\"}", arena);
    return resp;
  }

  const char *token = auth_header + 7;
  if (strlen(g_upload_auth_token) == 0 || strcmp(token, g_upload_auth_token) != 0)
  {
    Dowa_HashMap_Push_Arena(resp, "status", "403", arena);
    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Invalid token\"}", arena);
    return resp;
  }

  // Parse request body for filename and content_type
  void *body_kv = Dowa_HashMap_Get_Ptr(req, "Body");
  if (!body_kv)
  {
    Dowa_HashMap_Push_Arena(resp, "status", "400", arena);
    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Missing request body\"}", arena);
    return resp;
  }

  const char *body = ((Seobeo_Request_Entry*)body_kv)->value;

  // Simple JSON parsing for filename and content_type
  char filename[256] = {0};
  char content_type[128] = "application/octet-stream";

  // Find "filename":"value"
  const char *fn_key = strstr(body, "\"filename\"");
  if (fn_key)
  {
    const char *fn_start = strchr(fn_key + 10, '"');
    if (fn_start)
    {
      fn_start++;
      const char *fn_end = strchr(fn_start, '"');
      if (fn_end && (size_t)(fn_end - fn_start) < sizeof(filename))
      {
        memcpy(filename, fn_start, fn_end - fn_start);
        filename[fn_end - fn_start] = '\0';
      }
    }
  }

  // Find "content_type":"value"
  const char *ct_key = strstr(body, "\"content_type\"");
  if (ct_key)
  {
    const char *ct_start = strchr(ct_key + 14, '"');
    if (ct_start)
    {
      ct_start++;
      const char *ct_end = strchr(ct_start, '"');
      if (ct_end && (size_t)(ct_end - ct_start) < sizeof(content_type))
      {
        memcpy(content_type, ct_start, ct_end - ct_start);
        content_type[ct_end - ct_start] = '\0';
      }
    }
  }

  if (strlen(filename) == 0)
  {
    Dowa_HashMap_Push_Arena(resp, "status", "400", arena);
    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
    Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Missing filename in request body\"}", arena);
    return resp;
  }

  // Generate unique S3 key with timestamp
  char s3_key[512];
  char *uuid = Dowa_Arena_Allocate(arena, UUID_LEN);
  uint32 seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++;
  Dowa_String_UUID(seed, uuid);
  snprintf(s3_key, sizeof(s3_key), "uploads/%s/%s", uuid, filename);

  // Generate presigned URL
  S3_Presigned_URL presigned = S3_Presign_Put(&g_s3_config, s3_key, content_type, g_s3_url_expires);

  if (!presigned.success)
  {
    Dowa_HashMap_Push_Arena(resp, "status", "500", arena);
    Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
    char *error_body = Dowa_Arena_Allocate(arena, 256);
    snprintf(error_body, 256, "{\"error\":\"Failed to generate upload URL: %s\"}",
             presigned.error_message ? presigned.error_message : "unknown");
    Dowa_HashMap_Push_Arena(resp, "body", error_body, arena);
    S3_Presigned_URL_Destroy(&presigned);
    return resp;
  }

  // Build response
  char *response_body = Dowa_Arena_Allocate(arena, 2048 + strlen(presigned.url));
  snprintf(response_body, 2048 + strlen(presigned.url),
           "{\"upload_url\":\"%s\",\"key\":\"%s\",\"expires\":%d}",
           presigned.url, s3_key, g_s3_url_expires);

  S3_Presigned_URL_Destroy(&presigned);

  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("[S3] Generated upload URL for: %s\n", s3_key);

  return resp;
}

int main(void)
{
  // Load server config
  load_config("mrjunejune/.config");

  // Load S3 credentials from .env
  FILE *env_file = fopen(".env", "r");
  static char s3_access_key[128] = {0};
  static char s3_secret_key[128] = {0};

  if (env_file)
  {
    char line[512];
    while (fgets(line, sizeof(line), env_file))
    {
      if (strncmp(line, "AWS_MRJUNEJUNE_ACCESS_KEY=", 26) == 0)
      {
        char *val = line + 26;
        size_t len = strlen(val);
        while (len > 0 && (val[len-1] == '\n' || val[len-1] == '\r')) val[--len] = '\0';
        strncpy(s3_access_key, val, sizeof(s3_access_key) - 1);
      }
      else if (strncmp(line, "AWS_MRJUNEJUNE_SECRET_ACCESS_KEY=", 33) == 0)
      {
        char *val = line + 33;
        size_t len = strlen(val);
        while (len > 0 && (val[len-1] == '\n' || val[len-1] == '\r')) val[--len] = '\0';
        strncpy(s3_secret_key, val, sizeof(s3_secret_key) - 1);
      }
    }
    fclose(env_file);
  }

  // Initialize S3 config
  g_s3_config.access_key_id = s3_access_key;
  g_s3_config.secret_access_key = s3_secret_key;
  g_s3_config.region = g_s3_region;
  g_s3_config.bucket = g_s3_bucket;
  g_s3_config.endpoint = NULL;
  g_s3_config.use_path_style = FALSE;

  printf("[S3] Configured: region=%s, bucket=%s, key=%s...\n",
         g_s3_region, g_s3_bucket, s3_access_key[0] ? "***" : "(missing)");

  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);

  // -- S3 Upload --/
  Seobeo_Router_Register("POST", "/api/s3/upload-url", GetS3UploadUrl);

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

  // -- Talk --/
  Seobeo_Router_Register("GET", "/talk", GetTalk);
  Seobeo_Router_Register("GET", "/talk/index.html", GetRedirectTalk);

  Seobeo_WebSocket_Server_Init();
  Seobeo_WebSocket_Server_Register("/chat", Chat_Handler, NULL);

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