#include "HttpControlServer.h"
#include "RuntimeControlCommand.h"

#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>

namespace
{
int gFailures = 0;

void Expect(bool condition, const std::string& message)
{
	if (condition)
		return;

	++gFailures;
	std::cerr << "FAILED: " << message << "\n";
}

void ExpectEquals(const std::string& actual, const std::string& expected, const std::string& message)
{
	if (actual == expected)
		return;

	++gFailures;
	std::cerr << "FAILED: " << message << "\n"
		<< "expected: " << expected << "\n"
		<< "actual:   " << actual << "\n";
}

void TestParsesHttpRequest()
{
	using namespace RenderCadenceCompositor;

	HttpControlServer::HttpRequest request;
	const bool parsed = HttpControlServer::ParseHttpRequest(
		"GET /api/state?cacheBust=1 HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n",
		request);

	Expect(parsed, "request parses");
	ExpectEquals(request.method, "GET", "method is parsed");
	ExpectEquals(request.path, "/api/state", "query string is stripped from path");
	ExpectEquals(request.headers["host"], "127.0.0.1", "headers are lower-cased and trimmed");
}

void TestStateEndpointUsesCallback()
{
	using namespace RenderCadenceCompositor;

	HttpControlServer server;
	HttpControlServerCallbacks callbacks;
	callbacks.getStateJson = []() { return std::string("{\"ok\":true}"); };
	server.SetCallbacksForTest(callbacks);

	HttpControlServer::HttpRequest request;
	request.method = "GET";
	request.path = "/api/state";

	const HttpControlServer::HttpResponse response = server.RouteRequestForTest(request);
	ExpectEquals(response.status, "200 OK", "state endpoint succeeds");
	ExpectEquals(response.contentType, "application/json", "state endpoint is JSON");
	ExpectEquals(response.body, "{\"ok\":true}", "state endpoint returns callback JSON");
}

void TestWebSocketAcceptKey()
{
	using namespace RenderCadenceCompositor;

	const std::string acceptKey = HttpControlServer::WebSocketAcceptKey("dGhlIHNhbXBsZSBub25jZQ==");
	ExpectEquals(acceptKey, "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=", "WebSocket accept key matches RFC example");
}

void TestRootServesUiIndex()
{
	using namespace RenderCadenceCompositor;

	const std::filesystem::path root = std::filesystem::temp_directory_path() / "render-cadence-compositor-ui-test";
	std::filesystem::create_directories(root);
	{
		std::ofstream output(root / "index.html", std::ios::binary);
		output << "<!doctype html><div id=\"root\"></div>";
	}

	HttpControlServer server;
	HttpControlServer::HttpRequest request;
	request.method = "GET";
	request.path = "/";

	server.SetRootsForTest(root, std::filesystem::path());
	const HttpControlServer::HttpResponse response = server.RouteRequestForTest(request);

	ExpectEquals(response.status, "200 OK", "root endpoint serves UI index");
	ExpectEquals(response.contentType, "text/html", "UI index content type is html");
	Expect(response.body.find("root") != std::string::npos, "UI index body is returned");

	std::filesystem::remove_all(root);
}

void TestKnownPostEndpointReturnsActionError()
{
	using namespace RenderCadenceCompositor;

	HttpControlServer server;
	HttpControlServer::HttpRequest request;
	request.method = "POST";
	request.path = "/api/layers/add";
	request.body = "{\"shaderId\":\"happy-accident\"}";

	const HttpControlServer::HttpResponse response = server.RouteRequestForTest(request);
	ExpectEquals(response.status, "400 Bad Request", "unimplemented post returns OpenAPI action error status");
	ExpectEquals(response.contentType, "application/json", "unimplemented post returns JSON");
	Expect(response.body.find("\"ok\":false") != std::string::npos, "unimplemented post reports ok false");
	Expect(response.body.find("not implemented") != std::string::npos, "unimplemented post reports diagnostic");
}

void TestLayerPostEndpointsUseCallbacks()
{
	using namespace RenderCadenceCompositor;

	HttpControlServer server;
	HttpControlServerCallbacks callbacks;
	callbacks.addLayer = [](const std::string& body) {
		Expect(body.find("solid") != std::string::npos, "add callback receives request body");
		return ControlActionResult{ true, std::string() };
	};
	callbacks.removeLayer = [](const std::string& body) {
		Expect(body.find("runtime-layer-1") != std::string::npos, "remove callback receives request body");
		return ControlActionResult{ false, "Unknown layer id." };
	};
	server.SetCallbacksForTest(callbacks);

	HttpControlServer::HttpRequest addRequest;
	addRequest.method = "POST";
	addRequest.path = "/api/layers/add";
	addRequest.body = "{\"shaderId\":\"solid\"}";
	const HttpControlServer::HttpResponse addResponse = server.RouteRequestForTest(addRequest);
	ExpectEquals(addResponse.status, "200 OK", "add layer callback success returns 200");
	Expect(addResponse.body.find("\"ok\":true") != std::string::npos, "add layer callback returns action success");

	HttpControlServer::HttpRequest removeRequest;
	removeRequest.method = "POST";
	removeRequest.path = "/api/layers/remove";
	removeRequest.body = "{\"layerId\":\"runtime-layer-1\"}";
	const HttpControlServer::HttpResponse removeResponse = server.RouteRequestForTest(removeRequest);
	ExpectEquals(removeResponse.status, "400 Bad Request", "remove layer callback failure returns 400");
	Expect(removeResponse.body.find("Unknown layer id.") != std::string::npos, "remove layer callback returns diagnostic");
}

void TestGenericPostCallbackHandlesControlRoutes()
{
	using namespace RenderCadenceCompositor;

	HttpControlServer server;
	HttpControlServerCallbacks callbacks;
	callbacks.executePost = [](const std::string& path, const std::string& body) {
		ExpectEquals(path, "/api/layers/set-bypass", "generic callback receives route path");
		Expect(body.find("runtime-layer-1") != std::string::npos, "generic callback receives request body");
		return ControlActionResult{ true, std::string() };
	};
	server.SetCallbacksForTest(callbacks);

	HttpControlServer::HttpRequest request;
	request.method = "POST";
	request.path = "/api/layers/set-bypass";
	request.body = "{\"layerId\":\"runtime-layer-1\",\"bypass\":true}";

	const HttpControlServer::HttpResponse response = server.RouteRequestForTest(request);
	ExpectEquals(response.status, "200 OK", "generic control callback success returns 200");
	Expect(response.body.find("\"ok\":true") != std::string::npos, "generic control callback returns action success");
}

void TestReloadRouteParsesAsControlCommand()
{
	using namespace RenderCadenceCompositor;

	RuntimeControlCommand command;
	std::string error;
	Expect(ParseRuntimeControlCommand("/api/reload", "{}", command, error), "reload route parses as a control command");
	Expect(command.type == RuntimeControlCommandType::ReloadShaders, "reload route maps to reload command type");
}

void TestUnknownEndpointReturns404()
{
	using namespace RenderCadenceCompositor;

	HttpControlServer server;
	HttpControlServer::HttpRequest request;
	request.method = "GET";
	request.path = "/api/nope";

	const HttpControlServer::HttpResponse response = server.RouteRequestForTest(request);
	ExpectEquals(response.status, "404 Not Found", "unknown endpoint returns 404");
}
}

int main()
{
	TestParsesHttpRequest();
	TestStateEndpointUsesCallback();
	TestWebSocketAcceptKey();
	TestRootServesUiIndex();
	TestKnownPostEndpointReturnsActionError();
	TestLayerPostEndpointsUseCallbacks();
	TestGenericPostCallbackHandlesControlRoutes();
	TestReloadRouteParsesAsControlCommand();
	TestUnknownEndpointReturns404();

	if (gFailures != 0)
	{
		std::cerr << gFailures << " RenderCadenceCompositorHttpControlServer test failure(s).\n";
		return 1;
	}

	std::cout << "RenderCadenceCompositorHttpControlServer tests passed.\n";
	return 0;
}
