#include "connectors/auth_http_adapter.h"

#include <openssl/crypto.h>

#include <string.h>
#include <time.h>

static int32 hex_value(char value)
{
  if (value >= '0' && value <= '9')
    return value - '0';
  if (value >= 'a' && value <= 'f')
    return value - 'a' + 10;
  if (value >= 'A' && value <= 'F')
    return value - 'A' + 10;
  return -1;
}

static boolean decode_secret(
    const char *hex, uint8 *output, size_t capacity, size_t *length_out)
{
  if (!hex || !output || !length_out)
    return FALSE;
  size_t length = strlen(hex);
  if (length % 2 != 0 || length / 2 > capacity ||
      length / 2 < AUTH_CRYPTO_COOKIE_SECRET_MIN_BYTES)
    return FALSE;
  for (size_t i = 0; i < length; i += 2) {
    int32 high = hex_value(hex[i]);
    int32 low = hex_value(hex[i + 1]);
    if (high < 0 || low < 0) {
      OPENSSL_cleanse(output, capacity);
      return FALSE;
    }
    output[i / 2] = (uint8)((high << 4) | low);
  }
  *length_out = length / 2;
  return TRUE;
}

boolean Connector_Auth_HTTP_Init(
    Connector_Auth_HTTP_Context *context,
    const char *database_path,
    const char *cookie_secret_hex,
    int64 session_idle_ttl_secs)
{
  if (!context || !database_path || !cookie_secret_hex ||
      session_idle_ttl_secs <= 0)
    return FALSE;
  memset(context, 0, sizeof(*context));
  if (!decode_secret(
          cookie_secret_hex, context->cookie_secret,
          sizeof(context->cookie_secret), &context->cookie_secret_length))
    return FALSE;
  context->store = Auth_Store_Create(database_path);
  if (!context->store) {
    OPENSSL_cleanse(context->cookie_secret, sizeof(context->cookie_secret));
    context->cookie_secret_length = 0;
    return FALSE;
  }
  context->session_idle_ttl_secs = session_idle_ttl_secs;
  return TRUE;
}

void Connector_Auth_HTTP_Destroy(Connector_Auth_HTTP_Context *context)
{
  if (!context)
    return;
  if (context->store)
    Auth_Store_Destroy(context->store);
  OPENSSL_cleanse(context, sizeof(*context));
}

static const char *resolve_user(
    Seobeo_Request_Entry *request,
    boolean require_csrf,
    Dowa_Arena *arena,
    void *opaque)
{
  Connector_Auth_HTTP_Context *context = opaque;
  if (!context || !context->store || !arena)
    return NULL;
  Auth_HTTP_Authenticated_User authenticated;
  Auth_HTTP_Resolve_Result result = Auth_HTTP_Resolve_Authenticated_User(
      request, context->store, context->cookie_secret,
      context->cookie_secret_length, (int64)time(NULL),
      context->session_idle_ttl_secs, &authenticated);
  if (result != AUTH_HTTP_RESOLVE_OK)
    return NULL;
  if (require_csrf && !Auth_HTTP_Verify_CSRF(
          request, context->cookie_secret, context->cookie_secret_length,
          authenticated.token_digest)) {
    OPENSSL_cleanse(&authenticated, sizeof(authenticated));
    return NULL;
  }
  size_t id_length = strlen(authenticated.user.id);
  char *user_id = Dowa_Arena_Allocate(arena, id_length + 1);
  if (user_id)
    memcpy(user_id, authenticated.user.id, id_length + 1);
  OPENSSL_cleanse(&authenticated, sizeof(authenticated));
  return user_id;
}

static boolean resolve_session(
    Seobeo_Request_Entry *request,
    Connector_Auth_Session *session,
    void *opaque)
{
  Connector_Auth_HTTP_Context *context = opaque;
  if (!context || !context->store || !session)
    return FALSE;
  Auth_HTTP_Authenticated_User authenticated;
  Auth_HTTP_Resolve_Result result = Auth_HTTP_Resolve_Authenticated_User(
      request, context->store, context->cookie_secret,
      context->cookie_secret_length, (int64)time(NULL),
      context->session_idle_ttl_secs, &authenticated);
  if (result != AUTH_HTTP_RESOLVE_OK)
    return FALSE;
  memset(session, 0, sizeof(*session));
  snprintf(session->user_id, sizeof(session->user_id), "%s",
           authenticated.user.id);
  snprintf(session->username, sizeof(session->username), "%s",
           authenticated.user.username);
  snprintf(session->role, sizeof(session->role), "%s",
           authenticated.user.role);
  snprintf(session->csrf_token, sizeof(session->csrf_token), "%s",
           authenticated.csrf_token);
  OPENSSL_cleanse(&authenticated, sizeof(authenticated));
  return TRUE;
}

Connector_Auth_Adapter Connector_Auth_HTTP_Create_Adapter(
    Connector_Auth_HTTP_Context *context)
{
  Connector_Auth_Adapter adapter = {
    .resolve_user = resolve_user,
    .resolve_session = resolve_session,
    .context = context
  };
  return adapter;
}
