#include "httpd.h"
#include <sys/stat.h>
#include <openssl/evp.h>
#include <sqlite3.h>
#include <uuid/uuid.h>

#define CHUNK_SIZE 1024 // read 1024 bytes at a time

// Public directory settings
#define PUBLIC_DIR "./public"
#define INDEX_HTML "/index.html"
#define NOT_FOUND_HTML "/notfound/404.html"

/*
Key-value structure pairs
*/
typedef struct {
   char *key;
   char *value;
} KeyValuePair;

// Function to create key-value pair
KeyValuePair* createKeyValuePair(const char *key, const char *value) {
    KeyValuePair *pair = malloc(sizeof(KeyValuePair));
    if (pair != NULL) {
        pair->key = strdup(key);
        pair->value = strdup(value);
    }
    return pair;
}

// Function to free memory occupied by a key-value pair
void freeKeyValuePair(KeyValuePair *pair) {
    if (pair != NULL) {
        free(pair->key);
        free(pair->value);
        free(pair);
    }
}

/*
SHA-512 Hash of string
*/
void sha512_hash_string(const char *input, char outputBuffer[129]) {
    EVP_MD_CTX *mdctx;
    const EVP_MD *md;
    unsigned char hash[EVP_MAX_MD_SIZE];
    unsigned int hashLen;

    md = EVP_sha512();
    mdctx = EVP_MD_CTX_new();

    EVP_DigestInit_ex(mdctx, md, NULL);
    EVP_DigestUpdate(mdctx, input, strlen(input));
    EVP_DigestFinal_ex(mdctx, hash, &hashLen);

    EVP_MD_CTX_free(mdctx);

    for (int i = 0; i < hashLen; i++) {
        sprintf(&outputBuffer[i * 2], "%02x", hash[i]);
    }
    outputBuffer[hashLen * 2] = '\0';
}

/*
Process POST to /hash
*/
void parsePasswordPostData(const char *postData) {
    char *dataCopy = strdup(postData);  // Make a copy to avoid modifying the original string
    char *token = strtok(dataCopy, "&");

    while (token != NULL) {
        char *keyValuePair = strdup(token);
        char *key = strtok(keyValuePair, "=");
        char *value = strtok(NULL, "=");

        if (key != NULL && value != NULL) {
            //printf("Key: %s, Value: %s\n", key, value);
            // Here, you can store or process the key and value as needed
            if(*key == 'p') {
                char localHashOutput[129];
                sha512_hash_string(value, localHashOutput);
                //printf("Hashed");
                printf("Input password is: %s\nHashed password is: %s\n", value, localHashOutput);
            }
        }

        free(keyValuePair);
        token = strtok(NULL, "&");
    }

    free(dataCopy);
}

static int callback(void *NotUsed, int argc, char **argv, char **azColName){
  int i;
  for(i = 0; i<argc; i++){
     printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
  }
  printf("\n");
  return 0;
}

/*
Main Function
*/
int main(int c, char **v) {
	sqlite3 *db;
	char *err_msg = 0;

	int rc = sqlite3_open("infinitum.db", &db);
	if (rc != SQLITE_OK) {
		fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
		sqlite3_close(db);
		return 1;
	}

	char *sql;
	sql = "CREATE TABLE IF NOT EXISTS Users ( \
		UserID TEXT PRIMARY KEY, \
		FirstName TEXT NOT NULL, \
		LastName TEXT NOT NULL, \
		Email TEXT NOT NULL UNIQUE, \
		RegistrationDate TIMESTAMP DEFAULT CURRENT_TIMESTAMP, \
		LastLoginDate TIMESTAMP, \
		Notes TEXT \
		);";

	rc = sqlite3_exec(db, sql, callback, 0, &err_msg);

	if( rc != SQLITE_OK) {
		fprintf(stderr, "Something happend during processing db tables");
		sqlite3_free(&err_msg);
		sqlite3_close(db);
		return 1;
	}

	// idk
	char *port = c == 1 ? "8000" : v[1];
	serve_forever(port);
	return 0;
}

int file_exists(const char *file_name) {
  struct stat buffer;
  int exists;

  exists = (stat(file_name, &buffer) == 0);

  return exists;
}

int read_file(const char *file_name) {
  char buf[CHUNK_SIZE];
  FILE *file;
  size_t nread;
  int err = 1;

  file = fopen(file_name, "r");

  if (file) {
    while ((nread = fread(buf, 1, sizeof buf, file)) > 0)
      fwrite(buf, 1, nread, stdout);

    err = ferror(file);
    fclose(file);
  }
  return err;
}

/*
Route Handling
*/
void route() {
  ROUTE_START()

  GET("/") {
    char index_html[20];
    sprintf(index_html, "%s%s", PUBLIC_DIR, INDEX_HTML);

    HTTP_200;
    if (file_exists(index_html)) {
      read_file(index_html);
    } else {
      printf("Hello! You are using %s\n\n", request_header("User-Agent"));
    }
  }

  POST("/hash") {
        HTTP_201;
        if(payload_size > 0){
           parsePasswordPostData(payload);
        }
  }

  /*GET("/test") {
    HTTP_200;
    printf("List of request headers:\n\n");

    header_t *h = request_headers();

    while (h->name) {
      printf("%s: %s\n", h->name, h->value);
      h++;
    }
  }*/

  /*POST("/") {
    HTTP_201;
    printf("Wow, seems that you POSTed %d bytes. \n", payload_size);
    printf("Fetch the data using `payload` variable.\n");
    if (payload_size > 0)
      printf("Request body: %s", payload);
  }*/;

  GET(uri) {
    char file_name[255];
    sprintf(file_name, "%s%s", PUBLIC_DIR, uri);

    if (file_exists(file_name)) {
      HTTP_200;
      read_file(file_name);
    } else {
      HTTP_404;
      sprintf(file_name, "%s%s", PUBLIC_DIR, NOT_FOUND_HTML);
      if (file_exists(file_name))
        read_file(file_name);
    }
  }

  ROUTE_END()
}