#include "httpd.js.h"

#include "http.h"
#include "log.h"
#include "mem.h"
#include "ssb.db.h"
#include "ssb.h"
#include "task.h"
#include "util.js.h"

typedef struct _save_t
{
	tf_http_request_t* request;
	int response;
	char blob_id[k_blob_id_len];
} save_t;

static void _httpd_endpoint_save_work(tf_ssb_t* ssb, void* user_data)
{
	save_t* save = user_data;
	tf_http_request_t* request = save->request;
	const char* session = tf_http_get_cookie(tf_http_request_get_header(request, "cookie"), "session");

	JSMallocFunctions funcs = { 0 };
	tf_get_js_malloc_functions(&funcs);
	JSRuntime* runtime = JS_NewRuntime2(&funcs, NULL);
	JSContext* context = JS_NewContext(runtime);

	JSValue jwt = tf_httpd_authenticate_jwt(ssb, context, session);
	JSValue user = JS_GetPropertyStr(context, jwt, "name");
	const char* user_string = JS_ToCString(context, user);

	if (user_string && tf_httpd_is_name_valid(user_string))
	{
		tf_httpd_user_app_t* user_app = tf_httpd_parse_user_app_from_path(request->path, "/save");
		if (user_app)
		{
			if (strcmp(user_string, user_app->user) == 0 || (strcmp(user_app->user, "core") == 0 && tf_ssb_db_user_has_permission(ssb, NULL, user_string, "administration")))
			{
				size_t path_length = strlen("path:") + strlen(user_app->app) + 1;
				char* app_path = tf_malloc(path_length);
				snprintf(app_path, path_length, "path:%s", user_app->app);

				const char* old_blob_id = tf_ssb_db_get_property(ssb, user_app->user, app_path);

				JSValue new_app = JS_ParseJSON(context, request->body, request->content_length, NULL);
				tf_util_report_error(context, new_app);
				if (JS_IsObject(new_app))
				{
					uint8_t* old_blob = NULL;
					size_t old_blob_size = 0;
					if (tf_ssb_db_blob_get(ssb, old_blob_id, &old_blob, &old_blob_size))
					{
						JSValue old_app = JS_ParseJSON(context, (const char*)old_blob, old_blob_size, NULL);
						if (JS_IsObject(old_app))
						{
							JSAtom previous = JS_NewAtom(context, "previous");
							JS_DeleteProperty(context, old_app, previous, 0);
							JS_DeleteProperty(context, new_app, previous, 0);

							JSValue old_app_json = JS_JSONStringify(context, old_app, JS_NULL, JS_NULL);
							JSValue new_app_json = JS_JSONStringify(context, new_app, JS_NULL, JS_NULL);
							const char* old_app_str = JS_ToCString(context, old_app_json);
							const char* new_app_str = JS_ToCString(context, new_app_json);

							if (old_app_str && new_app_str && strcmp(old_app_str, new_app_str) == 0)
							{
								snprintf(save->blob_id, sizeof(save->blob_id), "/%s", old_blob_id);
								save->response = 200;
							}

							JS_FreeCString(context, old_app_str);
							JS_FreeCString(context, new_app_str);
							JS_FreeValue(context, old_app_json);
							JS_FreeValue(context, new_app_json);
							JS_FreeAtom(context, previous);
						}
						JS_FreeValue(context, old_app);
						tf_free(old_blob);
					}

					if (!save->response)
					{
						if (old_blob_id)
						{
							JS_SetPropertyStr(context, new_app, "previous", JS_NewString(context, old_blob_id));
						}
						JSValue new_app_json = JS_JSONStringify(context, new_app, JS_NULL, JS_NULL);
						size_t new_app_length = 0;
						const char* new_app_str = JS_ToCStringLen(context, &new_app_length, new_app_json);

						char blob_id[k_blob_id_len] = { 0 };
						if (tf_ssb_db_blob_store(ssb, (const uint8_t*)new_app_str, new_app_length, blob_id, sizeof(blob_id), NULL) &&
							tf_ssb_db_set_property(ssb, user_app->user, app_path, blob_id))
						{
							tf_ssb_db_add_value_to_array_property(ssb, user_app->user, "apps", user_app->app);
							tf_string_set(save->blob_id, sizeof(save->blob_id), blob_id);
							save->response = 200;
						}
						else
						{
							tf_printf("Blob store or property set failed.\n");
							save->response = 500;
						}

						JS_FreeCString(context, new_app_str);
						JS_FreeValue(context, new_app_json);
					}
				}
				else
				{
					save->response = 400;
				}
				JS_FreeValue(context, new_app);

				tf_free(app_path);
				tf_free((void*)old_blob_id);
			}
			else
			{
				save->response = 403;
			}
			tf_free(user_app);
		}
		else if (strcmp(request->path, "/save") == 0)
		{
			char blob_id[k_blob_id_len] = { 0 };
			if (tf_ssb_db_blob_store(ssb, request->body, request->content_length, blob_id, sizeof(blob_id), NULL))
			{
				tf_string_set(save->blob_id, sizeof(save->blob_id), blob_id);
				save->response = 200;
			}
			else
			{
				tf_printf("Blob store failed.\n");
				save->response = 500;
			}
		}
		else
		{
			save->response = 400;
		}
	}
	else
	{
		save->response = 401;
	}

	tf_free((void*)session);
	JS_FreeCString(context, user_string);
	JS_FreeValue(context, user);
	JS_FreeValue(context, jwt);
	JS_FreeContext(context);
	JS_FreeRuntime(runtime);
}

static void _httpd_endpoint_save_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
	save_t* save = user_data;
	tf_http_request_t* request = save->request;
	if (*save->blob_id)
	{
		char body[256] = "";
		int length = snprintf(body, sizeof(body), "/%s", save->blob_id);
		tf_http_respond(request, 200, NULL, 0, body, length);
	}
	tf_http_request_unref(request);
	tf_free(save);
}

void tf_httpd_endpoint_save(tf_http_request_t* request)
{
	tf_http_request_ref(request);
	tf_task_t* task = request->user_data;
	tf_ssb_t* ssb = tf_task_get_ssb(task);
	save_t* save = tf_malloc(sizeof(save_t));
	*save = (save_t) {
		.request = request,
	};
	tf_ssb_run_work(ssb, _httpd_endpoint_save_work, _httpd_endpoint_save_after_work, save);
}
