#include "httpd.js.h"

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

typedef struct _view_t
{
	tf_http_request_t* request;
	const char** form_data;
	void* data;
	size_t size;
	char etag[256];
	char notify_want_blob_id[k_blob_id_len];
	bool not_modified;
} view_t;

static bool _is_filename_safe(const char* filename)
{
	if (!filename)
	{
		return NULL;
	}
	for (const char* p = filename; *p; p++)
	{
		if ((*p <= 'a' && *p >= 'z') && (*p <= 'A' && *p >= 'Z') && (*p <= '0' && *p >= '9') && *p != '.' && *p != '-' && *p != '_')
		{
			return false;
		}
	}
	return strlen(filename) < 256;
}

static void _httpd_endpoint_view_work(tf_ssb_t* ssb, void* user_data)
{
	view_t* view = user_data;
	tf_http_request_t* request = view->request;
	char blob_id[k_blob_id_len] = "";

	tf_httpd_user_app_t* user_app = tf_httpd_parse_user_app_from_path(request->path, "/view");
	if (user_app)
	{
		size_t app_path_length = strlen("path:") + strlen(user_app->app) + 1;
		char* app_path = tf_malloc(app_path_length);
		snprintf(app_path, app_path_length, "path:%s", user_app->app);
		const char* value = tf_ssb_db_get_property(ssb, user_app->user, app_path);
		tf_string_set(blob_id, sizeof(blob_id), value);
		tf_free(app_path);
		tf_free((void*)value);
	}
	else if (request->path[0] == '/' && request->path[1] == '&')
	{
		snprintf(blob_id, sizeof(blob_id), "%.*s", (int)(strlen(request->path) - strlen("/view") - 1), request->path + 1);
	}
	tf_free(user_app);

	if (*blob_id)
	{
		snprintf(view->etag, sizeof(view->etag), "\"%s\"", blob_id);
		const char* if_none_match = tf_http_request_get_header(request, "if-none-match");
		char match[258];
		snprintf(match, sizeof(match), "\"%s\"", blob_id);
		if (if_none_match && strcmp(if_none_match, match) == 0)
		{
			view->not_modified = true;
		}
		else
		{
			if (!tf_ssb_db_blob_get(ssb, blob_id, (uint8_t**)&view->data, &view->size))
			{
				sqlite3* db = tf_ssb_acquire_db_writer(ssb);
				tf_ssb_db_add_blob_wants(db, blob_id);
				tf_ssb_release_db_writer(ssb, db);
				tf_string_set(view->notify_want_blob_id, sizeof(view->notify_want_blob_id), blob_id);
			}
		}
	}
}

static void _httpd_endpoint_view_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
	view_t* view = user_data;
	const char* filename = tf_httpd_form_data_get(view->form_data, "filename");
	if (!_is_filename_safe(filename))
	{
		filename = NULL;
	}
	char content_disposition[512] = "";
	if (filename)
	{
		snprintf(content_disposition, sizeof(content_disposition), "attachment; filename=%s", filename);
	}
	const char* headers[] = {
		"Content-Security-Policy",
		"sandbox allow-downloads allow-top-navigation-by-user-activation",
		"Content-Type",
		view->data ? tf_httpd_magic_bytes_to_content_type(view->data, view->size) : "text/plain",
		"etag",
		view->etag,
		filename ? "Content-Disposition" : NULL,
		filename ? content_disposition : NULL,
	};
	int count = filename ? tf_countof(headers) / 2 : (tf_countof(headers) / 2 - 1);
	if (view->not_modified)
	{
		tf_http_respond(view->request, 304, headers, count, NULL, 0);
	}
	else if (view->data)
	{
		tf_http_respond(view->request, 200, headers, count, view->data, view->size);
		tf_free(view->data);
	}
	else
	{
		const char* k_payload = tf_http_status_text(404);
		tf_http_respond(view->request, 404, NULL, 0, k_payload, strlen(k_payload));
	}

	if (*view->notify_want_blob_id)
	{
		tf_ssb_notify_blob_want_added(ssb, view->notify_want_blob_id);
	}

	tf_free(view->form_data);
	tf_http_request_unref(view->request);
	tf_free(view);
}

void tf_httpd_endpoint_view(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);
	view_t* view = tf_malloc(sizeof(view_t));
	*view = (view_t) { .request = request, .form_data = tf_httpd_form_data_decode(request->query, request->query ? strlen(request->query) : 0) };
	tf_ssb_run_work(ssb, _httpd_endpoint_view_work, _httpd_endpoint_view_after_work, view);
}
