#include "config.h"
#include <stdio.h>
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_MEMORY_H
#include <memory.h>
#endif
#include <curl/curl.h>
#include "auth_client.h"

static const char *kAuthApiPath = "/api/v1/auth";

struct auth_client {
  CURL *c;
  const char *service;
  const char *server;
};

static void auth_client_set_proto(auth_client_t ac, const char *proto) {
  char url[strlen(ac->server) + 32];
  sprintf(url, "%s://%s%s", proto, ac->server, kAuthApiPath);
  curl_easy_setopt(ac->c, CURLOPT_URL, url);
}

auth_client_t auth_client_new(const char *service, const char *server) {
  auth_client_t ac = (auth_client_t)malloc(sizeof(struct auth_client));

  ac->service = service;
  ac->server = server;
  ac->c = curl_easy_init();
  curl_easy_setopt(ac->c, CURLOPT_NOSIGNAL, 1);
  curl_easy_setopt(ac->c, CURLOPT_TIMEOUT, 60);
  auth_client_set_proto(ac, "http");
  return ac;
}

void auth_client_set_certificate(auth_client_t ac,
                                 const char *ca_file,
                                 const char *crt_file,
                                 const char *key_file) {
  curl_easy_setopt(ac->c, CURLOPT_CAPATH, ca_file);
  curl_easy_setopt(ac->c, CURLOPT_SSLCERTTYPE, "PEM");
  curl_easy_setopt(ac->c, CURLOPT_SSLCERT, crt_file);
  curl_easy_setopt(ac->c, CURLOPT_SSLKEYTYPE, "PEM");
  curl_easy_setopt(ac->c, CURLOPT_SSLKEY, key_file);
  curl_easy_setopt(ac->c, CURLOPT_SSL_VERIFYPEER, 1);
  curl_easy_setopt(ac->c, CURLOPT_SSL_VERIFYHOST, 0);
  auth_client_set_proto(ac, "https");
}

void auth_client_free(auth_client_t ac) {
  curl_easy_cleanup(ac->c);
  free(ac);
}

const char *auth_client_strerror(int err) {
  if (err < AC_ERR_CURL_BASE) {
    return curl_easy_strerror(auth_client_err_to_curl(err));
  }
  switch (err) {
  case AC_ERR_AUTHENTICATION_FAILURE:
    return "Authentication failure";
  case AC_ERR_OTP_REQUIRED:
    return "OTP required";
  default:
    return "Unknown error";
  }
}

struct responsebuf {
  char *buf;
  int size;
};

static void responsebuf_init(struct responsebuf *rbuf) {
  rbuf->buf = (char *)malloc(1);
  rbuf->size = 0;
}

static void responsebuf_free(struct responsebuf *rbuf) {
  free(rbuf->buf);
}

static size_t responsebuf_callback(void *contents, size_t size, size_t nmemb, void *userp) {
  size_t realsize = size * nmemb;
  struct responsebuf *rbuf = (struct responsebuf *)userp;

  rbuf->buf = (char *)realloc(rbuf->buf, rbuf->size + realsize + 1);
  if (rbuf->buf == NULL) {
    return 0;
  }
  memcpy(rbuf->buf + rbuf->size, contents, realsize);
  rbuf->size += realsize;
  rbuf->buf[rbuf->size] = 0;
  return realsize;
}

int auth_client_authenticate(auth_client_t ac,
                             const char *username,
                             const char *password,
                             const char *otp_token,
                             const char *source_ip) {
  struct curl_httppost *formpost = NULL;
  struct curl_httppost *formlast = NULL;
  struct responsebuf rbuf;
  CURLcode res;
  int retval;

  // Build the POST request contents.
  curl_formadd(&formpost, &formlast,
               CURLFORM_COPYNAME, "service",
               CURLFORM_COPYCONTENTS, ac->service,
               CURLFORM_END);
  curl_formadd(&formpost, &formlast,
               CURLFORM_COPYNAME, "username",
               CURLFORM_COPYCONTENTS, username,
               CURLFORM_END);
  if (password) {
    curl_formadd(&formpost, &formlast,
                 CURLFORM_COPYNAME, "password",
                 CURLFORM_COPYCONTENTS, password,
                 CURLFORM_END);
  }
  if (otp_token) {
    curl_formadd(&formpost, &formlast,
                 CURLFORM_COPYNAME, "otp",
                 CURLFORM_COPYCONTENTS, otp_token,
                 CURLFORM_END);
  }
  if (source_ip) {
    curl_formadd(&formpost, &formlast,
                 CURLFORM_COPYNAME, "source_ip",
                 CURLFORM_COPYCONTENTS, source_ip,
                 CURLFORM_END);
  }
  curl_easy_setopt(ac->c, CURLOPT_HTTPPOST, formpost);

  responsebuf_init(&rbuf);
  curl_easy_setopt(ac->c, CURLOPT_WRITEFUNCTION, responsebuf_callback);
  curl_easy_setopt(ac->c, CURLOPT_WRITEDATA, (void *)&rbuf);

  res = curl_easy_perform(ac->c);
  if (res == CURLE_OK) {
    // Check the auth server response.
    if (!strncmp(rbuf.buf, "OK", 2)) {
      retval = AC_OK;
    } else if (!strncmp(rbuf.buf, "OTP_REQUIRED", 12)) {
      retval = AC_ERR_OTP_REQUIRED;
    } else if (!strncmp(rbuf.buf, "ERROR", 5)) {
      retval = AC_ERR_AUTHENTICATION_FAILURE;
    } else {
      retval = AC_ERR_BAD_RESPONSE;
    }
  } else {
    retval = auth_client_err_from_curl(res);
  }

  curl_formfree(formpost);
  responsebuf_free(&rbuf);

  return retval;
}