#pragma once

#include "ControlActionResult.h"

#include <winsock2.h>

#include <atomic>
#include <chrono>
#include <filesystem>
#include <functional>
#include <map>
#include <mutex>
#include <string>
#include <thread>
#include <vector>

namespace RenderCadenceCompositor
{
struct HttpControlServerConfig
{
	unsigned short preferredPort = 8080;
	unsigned short portSearchCount = 20;
	std::chrono::milliseconds idleSleep = std::chrono::milliseconds(10);
};

struct HttpControlServerCallbacks
{
	std::function<std::string()> getStateJson;
	std::function<ControlActionResult(const std::string&)> addLayer;
	std::function<ControlActionResult(const std::string&)> removeLayer;
	std::function<ControlActionResult(const std::string&, const std::string&)> executePost;
};

class UniqueSocket
{
public:
	explicit UniqueSocket(SOCKET socket = INVALID_SOCKET);
	~UniqueSocket();

	UniqueSocket(const UniqueSocket&) = delete;
	UniqueSocket& operator=(const UniqueSocket&) = delete;

	UniqueSocket(UniqueSocket&& other) noexcept;
	UniqueSocket& operator=(UniqueSocket&& other) noexcept;

	SOCKET get() const { return mSocket; }
	bool valid() const { return mSocket != INVALID_SOCKET; }
	SOCKET release();
	void reset(SOCKET socket = INVALID_SOCKET);

private:
	SOCKET mSocket = INVALID_SOCKET;
};

class HttpControlServer
{
public:
	struct HttpRequest
	{
		std::string method;
		std::string path;
		std::map<std::string, std::string> headers;
		std::string body;
	};

	struct HttpResponse
	{
		std::string status;
		std::string contentType;
		std::string body;
	};

	HttpControlServer() = default;
	~HttpControlServer();

	HttpControlServer(const HttpControlServer&) = delete;
	HttpControlServer& operator=(const HttpControlServer&) = delete;

	bool Start(
		const std::filesystem::path& uiRoot,
		const std::filesystem::path& docsRoot,
		HttpControlServerConfig config,
		HttpControlServerCallbacks callbacks,
		std::string& error);
	void Stop();

	bool IsRunning() const { return mRunning.load(std::memory_order_acquire); }
	unsigned short Port() const { return mPort; }

	void SetCallbacksForTest(HttpControlServerCallbacks callbacks);
	void SetRootsForTest(const std::filesystem::path& uiRoot, const std::filesystem::path& docsRoot);
	HttpResponse RouteRequestForTest(const HttpRequest& request) const;

	static bool ParseHttpRequest(const std::string& rawRequest, HttpRequest& request);
	static std::string WebSocketAcceptKey(const std::string& clientKey);

private:
	void ThreadMain();
	bool TryAcceptClient();
	bool HandleClient(UniqueSocket clientSocket);
	bool HandleWebSocketClient(UniqueSocket clientSocket, const HttpRequest& request);
	void WebSocketClientMain(UniqueSocket clientSocket);
	void JoinFinishedClientThreads();
	bool SendResponse(SOCKET clientSocket, const HttpResponse& response) const;
	HttpResponse RouteRequest(const HttpRequest& request) const;
	HttpResponse ServeGet(const HttpRequest& request) const;
	HttpResponse ServePost(const HttpRequest& request) const;
	HttpResponse ServeOpenApiSpec() const;
	HttpResponse ServeSwaggerDocs() const;
	HttpResponse ServeUiAsset(const std::string& relativePath) const;
	std::string LoadTextFile(const std::filesystem::path& path) const;

	static HttpResponse JsonResponse(const std::string& status, const std::string& body);
	static HttpResponse TextResponse(const std::string& status, const std::string& body);
	static HttpResponse HtmlResponse(const std::string& status, const std::string& body);
	static std::string ActionResponse(bool ok, const std::string& error = std::string());
	static bool SendWebSocketText(SOCKET clientSocket, const std::string& text);
	static std::string GuessContentType(const std::filesystem::path& path);
	static bool IsSafeRelativePath(const std::filesystem::path& path);
	static std::string ToLower(std::string text);

	std::filesystem::path mUiRoot;
	std::filesystem::path mDocsRoot;
	HttpControlServerConfig mConfig;
	HttpControlServerCallbacks mCallbacks;
	UniqueSocket mListenSocket;
	std::thread mThread;
	std::mutex mClientThreadsMutex;
	std::vector<std::thread> mClientThreads;
	std::vector<std::thread> mFinishedClientThreads;
	std::atomic<bool> mRunning{ false };
	unsigned short mPort = 0;
	bool mWinsockStarted = false;
};
}
