#include "../beaker.h"
#include "beaker_globals.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

static void build_cookie_headers(char *cookie_headers_buffer,
                                 size_t buffer_size) {

  cookie_headers_buffer[0] = '\0';

  for (int i = 0; i < cookies_to_set_count; i++) {
    char single_cookie_header[MAX_VALUE_LEN * 2];

    snprintf(single_cookie_header, sizeof(single_cookie_header),
             "Set-Cookie: %s=%s", cookies_to_set[i].name,
             cookies_to_set[i].value);

    if (strlen(cookies_to_set[i].expires) > 0) {
      strncat(single_cookie_header, "; Expires=",
              sizeof(single_cookie_header) - strlen(single_cookie_header) - 1);
      strncat(single_cookie_header, cookies_to_set[i].expires,
              sizeof(single_cookie_header) - strlen(single_cookie_header) - 1);
    }

    if (strlen(cookies_to_set[i].path) > 0) {
      strncat(single_cookie_header, "; Path=",
              sizeof(single_cookie_header) - strlen(single_cookie_header) - 1);
      strncat(single_cookie_header, cookies_to_set[i].path,
              sizeof(single_cookie_header) - strlen(single_cookie_header) - 1);
    }

    if (cookies_to_set[i].http_only) {
      strncat(single_cookie_header, "; HttpOnly",
              sizeof(single_cookie_header) - strlen(single_cookie_header) - 1);
    }

    if (cookies_to_set[i].secure) {
      strncat(single_cookie_header, "; Secure",
              sizeof(single_cookie_header) - strlen(single_cookie_header) - 1);
    }

    strncat(single_cookie_header, "\r\n",
            sizeof(single_cookie_header) - strlen(single_cookie_header) - 1);

    if (strlen(cookie_headers_buffer) + strlen(single_cookie_header) <
        buffer_size) {
      strncat(cookie_headers_buffer, single_cookie_header,
              buffer_size - strlen(cookie_headers_buffer) - 1);
    } else {
      beaker_log(
          "WARN",
          "build_cookie_headers: Cookie headers buffer full, truncating\n");
      break;
    }
  }
}

void send_status(const char *status_line) {
  if (current_client_socket == -1) {
    beaker_log("ERROR",
               "send_status: No client socket set. Cannot send response.\n");
    return;
  }

  char http_response[BUFFER_SIZE];
  current_response_status = atoi(status_line);
  current_response_size = 0;
  snprintf(http_response, sizeof(http_response),
           "HTTP/1.1 %s\r\n"
           "Content-Length: 0\r\n"
           "Connection: close\r\n"
           "\r\n",
           status_line);

  if (send(current_client_socket, http_response, strlen(http_response), 0) <
      0) {
    beaker_log_errno_format("ERROR",
                            "send_status: Failed to send HTTP status.\n");
  }
}

void send_response(const char *html) {

  if (current_client_socket == -1) {
    beaker_log("ERROR",
               "send_response: No client socket set. Cannot send response.\n");
    return;
  }

  char http_response_header[BUFFER_SIZE * 2];
  int content_length = strlen(html);
  current_response_status = 200;
  current_response_size = content_length;
  char cookie_headers[BUFFER_SIZE];

  build_cookie_headers(cookie_headers, sizeof(cookie_headers));

  snprintf(http_response_header, sizeof(http_response_header),
           "HTTP/1.1 200 OK\r\n"
           "Content-Type: text/html; charset=UTF-8\r\n"
           "Content-Length: %d\r\n"
           "%s"
           "Connection: close\r\n"
           "\r\n",
           content_length, cookie_headers);

  if (send(current_client_socket, http_response_header,
           strlen(http_response_header), 0) < 0) {
    beaker_log_errno_format("ERROR",
                            "send_response: Failed to send HTTP header.\n");
    return;
  }

  if (send(current_client_socket, html, content_length, 0) < 0) {
    beaker_log_errno_format("ERROR",
                            "send_response: Failed to send HTML body.\n");
    return;
  }

  cookies_to_set_count = 0;
}

void send_redirect(const char *location) {

  if (current_client_socket == -1) {
    beaker_log("ERROR",
               "send_redirect: No client socket set. Cannot send redirect.\n");
    return;
  }

  char http_response_header[BUFFER_SIZE * 2];
  current_response_status = 302;
  current_response_size = 0;
  char cookie_headers[BUFFER_SIZE];

  build_cookie_headers(cookie_headers, sizeof(cookie_headers));

  snprintf(http_response_header, sizeof(http_response_header),
           "HTTP/1.1 302 Found\r\n"
           "Location: %s\r\n"
           "%s"
           "Connection: close\r\n"
           "\r\n",
           location, cookie_headers);

  if (send(current_client_socket, http_response_header,
           strlen(http_response_header), 0) < 0) {
    beaker_log_errno_format("ERROR",
                            "send_redirect: Failed to send redirect header.\n");
    return;
  }

  cookies_to_set_count = 0;
}

void set_cookie(const char *name, const char *value, const char *expires,
                const char *path, bool http_only, bool secure) {

  if (cookies_to_set_count >= MAX_COOKIES) {
    beaker_log("WARN",
               "set_cookie: Maximum number of cookies to set reached. Cannot "
               "set cookie '%s'.\n",
               name);
    return;
  }

  Cookie *new_cookie = &cookies_to_set[cookies_to_set_count];

  strncpy(new_cookie->name, name, MAX_KEY_LEN - 1);
  new_cookie->name[MAX_KEY_LEN - 1] = '\0';

  strncpy(new_cookie->value, value, MAX_VALUE_LEN - 1);
  new_cookie->value[MAX_VALUE_LEN - 1] = '\0';

  if (expires && strlen(expires) > 0) {
    strncpy(new_cookie->expires, expires, MAX_VALUE_LEN - 1);
    new_cookie->expires[MAX_VALUE_LEN - 1] = '\0';
  } else {
    new_cookie->expires[0] = '\0';
  }

  if (path && strlen(path) > 0) {
    strncpy(new_cookie->path, path, MAX_KEY_LEN - 1);
    new_cookie->path[MAX_KEY_LEN - 1] = '\0';
  } else {
    new_cookie->path[0] = '\0';
  }

  new_cookie->http_only = http_only;
  new_cookie->secure = secure;

  cookies_to_set_count++;
}

char *get_cookie(const char *cookie_name) {

  char *cookie_header_start = strstr(current_request_buffer, "\r\nCookie: ");
  if (cookie_header_start == NULL) {
    return NULL;
  }

  cookie_header_start += strlen("\r\nCookie: ");

  char *cookie_header_end = strstr(cookie_header_start, "\r\n");
  if (cookie_header_end == NULL) {

    cookie_header_end =
        (char *)(current_request_buffer + strlen(current_request_buffer));
  }

  size_t cookie_str_len = cookie_header_end - cookie_header_start;
  char *cookie_str = (char *)malloc(cookie_str_len + 1);
  if (cookie_str == NULL) {
    beaker_log_errno_format("ERROR",
                            "get_cookie: Allocation failed for cookie_str.\n");
    return NULL;
  }

  strncpy(cookie_str, cookie_header_start, cookie_str_len);
  cookie_str[cookie_str_len] = '\0';

  char *token;
  char *saveptr_cookie;

  token = strtok_r(cookie_str, ";", &saveptr_cookie);
  while (token != NULL) {

    while (*token == ' ') {
      token++;
    }

    char *equals_sign = strchr(token, '=');
    if (equals_sign != NULL) {
      size_t name_len = equals_sign - token;

      if (name_len == strlen(cookie_name) &&
          strncmp(token, cookie_name, name_len) == 0) {

        char *cookie_value = strdup(equals_sign + 1);
        if (cookie_value == NULL) {
          beaker_log_errno_format(
              "ERROR", "get_cookie: Allocation failed for cookie_value.\n");
        }
        free(cookie_str);
        return cookie_value;
      }
    }

    token = strtok_r(NULL, ";", &saveptr_cookie);
  }

  free(cookie_str);
  return NULL;
}
