#pragma once

#include <cassert>
#include <condition_variable>
#include <fcntl.h>
#include <map>
#include <memory>
#include <mutex>
#include <netinet/in.h>
#include <optional>
#include <queue>
#include <sstream>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <thread>
#include <unordered_map>
#include <vector>

namespace VWeb {

template <typename T> using Ref = std::shared_ptr<T>;
template <typename T> using Vector = std::vector<T>;
template <typename T, typename... Args>
constexpr Ref<T> CreateRef(Args &&...args) {
  return std::make_shared<T>(std::forward<Args>(args)...);
}
typedef std::mutex Mutex;

#pragma region SAFESTRUCTS
template <typename T, typename S> struct SafeMap {
  explicit SafeMap(size_t maxSize = -1UL) : m_MaxSize(maxSize), m_End(false){};
  bool Add(const T &t, S &x) {
    std::unique_lock<std::mutex> lck(m_Mutex);
    while (m_Data.size() == m_MaxSize && !m_End)
      return false;
    assert(!m_End);
    m_Data.emplace(t, std::move(x));
    return true;
  };
  bool Add(T &&t, S &&x) {
    std::unique_lock<std::mutex> lck(m_Mutex);
    while (m_Data.size() == m_MaxSize && !m_End)
      return false;
    assert(!m_End);
    m_Data.push(std::move(t));
    return true;
  };
  void Remove(T t) {
    std::unique_lock<std::mutex> lck(m_Mutex);
    if (m_Data.empty() || m_End)
      return;
    if (m_Data.contains(t))
      m_Data.erase(t);
    m_CVFull.notify_one();
  };
  void Clear() {
    std::unique_lock<std::mutex> lck(m_Mutex);
    std::unordered_map<T, S> empty;
    std::swap(m_Data, empty);
    m_CVFull.notify_all();
  };
  bool Has(T t) {
    std::unique_lock<std::mutex> lck(m_Mutex);
    return m_Data.contains(t);
  };
  S &Get(T t) {
    std::unique_lock<std::mutex> lck(m_Mutex);
    return m_Data[t];
  };
  int Size() { return m_Data.size(); };

private:
  std::unordered_map<T, S> m_Data{};
  std::mutex m_Mutex{};
  std::condition_variable m_CVFull{};
  const size_t m_MaxSize{};
  std::atomic<bool> m_End{};
};

template <typename T> struct SafeQueue {
  explicit SafeQueue(size_t maxSize = -1UL)
      : m_MaxSize(maxSize),
        m_End(false){};
  void Push(const T &t) {
    std::unique_lock<std::mutex> lck(m_Mutex);
    while (m_Queue.size() == m_MaxSize && !m_End)
      m_CVFull.wait(lck);
    assert(!m_End);
    m_Queue.push(std::move(t));
    m_CVEmpty.notify_one();
  };
  void Push(T &&t) {
    std::unique_lock<std::mutex> lck(m_Mutex);
    while (m_Queue.size() == m_MaxSize && !m_End)
      m_CVFull.wait(lck);
    assert(!m_End);
    m_Queue.push(std::move(t));
    m_CVEmpty.notify_one();
  };
  void Open() {
    m_End = false;
    std::lock_guard<std::mutex> lck(m_Mutex);
    m_CVEmpty.notify_all();
    m_CVFull.notify_all();
  };
  void Close() {
    m_End = true;
    std::lock_guard<std::mutex> lck(m_Mutex);
    m_CVEmpty.notify_all();
    m_CVFull.notify_all();
  };
  void Clear() {
    std::unique_lock<std::mutex> lck(m_Mutex);
    std::queue<T> empty;
    std::swap(m_Queue, empty);
    m_CVEmpty.notify_all();
    m_CVFull.notify_all();
  };
  std::optional<T> Pop() {
    std::unique_lock<std::mutex> lck(m_Mutex);
    if (m_Queue.empty() || m_End)
      return {};
    T t = std::move(m_Queue.front());
    m_Queue.pop();
    m_CVFull.notify_one();
    return t;
  };
  std::optional<T> WaitAndPop() {
    std::unique_lock<std::mutex> lck(m_Mutex);
    while (m_Queue.empty() && !m_End)
      m_CVEmpty.wait(lck);
    if (m_Queue.empty() || m_End)
      return {};
    T t = std::move(m_Queue.front());
    m_Queue.pop();
    m_CVFull.notify_one();
    return t;
  };
  bool IsClosed() { return m_End; }
  int Size() { return m_Queue.size(); }
  std::queue<T> &GetQueue() { return m_Queue; }
  void Flush() {
    while (!m_Queue.empty())
      m_CVEmpty.notify_one();
    m_End = true;
    m_CVEmpty.notify_all();
  }

private:
  std::queue<T> m_Queue;
  std::mutex m_Mutex;
  std::condition_variable m_CVEmpty, m_CVFull;
  const size_t m_MaxSize;
  std::atomic<bool> m_End;
};
#pragma endregion SAFESTRUCTS
#pragma region THREADING
struct WorkerJob {
  virtual ~WorkerJob() = default;
  virtual void Execute() = 0;
};
class ThreadPool {
public:
  explicit ThreadPool(const std::string &name);
  void SetThreadCount(int);
  void Dispatch(const Ref<WorkerJob> &);
  void Create();
  void Stop();
  void Execute();

protected:
  std::string m_Name;
  int m_ThreadCount{1};
  bool m_IsCreated{false};
  Vector<std::thread> m_Threads{};
  SafeQueue<Ref<WorkerJob>> m_Queue;
  bool m_IsDone{false};
};
#pragma endregion THREADING
#pragma region VWEB
enum class EPollReturns { OK = 0, BREAK, FAILURE };

class EPollManager;
class SocketManager;
struct ServerConfig {
  int Port{9020};
  int MaxBufferSize{-1};
  int BufferSize{16384};
  int WorkerThreads{-1};
  int MaxEvents{5000};
  bool AllowSharedLibs{true};
  Ref<EPollManager> EPoll{nullptr};
  Ref<SocketManager> Socket{nullptr};
  std::string ErrorDir;
  std::string MimeFiles;
};
struct Accept {
  EPollReturns ReturnValue{EPollReturns::OK};
  std::stringstream Data{};
  ssize_t CurrentBytes;
  int SockId{-1};
};
class EPollManager {
public:
  EPollManager();
  ~EPollManager();
  [[nodiscard]] bool Dispatch(int sock, uint32_t eventType = EPOLLIN) const;
  [[nodiscard]] bool UpdateEvents(int sock, uint32_t eventType = EPOLLIN) const;
  int Wait(int idx, epoll_event *events) const;

protected:
  int m_EpollID{-1};
};

struct SendData {
  ssize_t Offset{0};
  ssize_t Size{0};
  int SocketID{0};
  std::string Content;
};

enum class WriteState { OK = 0, EPOLL, ERRORED };

class SocketUtils {
public:
  static bool MakeAsync(int socketId);
  static bool Add(int socketId);
  static bool Close(int socketId);
  static bool WriteDone(int socketId);
  static WriteState Write(SendData &);
};
class SocketManager {
public:
  explicit SocketManager(const Ref<ServerConfig> &);
  ~SocketManager();
  Accept Handle();
  bool SetSendListen(int socketID);
  bool SetReadListen(int socketID);
  Ref<ServerConfig> &GetServerConfig() { return m_ServerConfig; }
  [[nodiscard]] int ID() const { return m_SocketID; }
  [[nodiscard]] bool IsErrored() const { return m_IsErrored; }
  void Init();

protected:
  void Errored(const std::string &data);

  int m_SocketID{};
  Ref<ServerConfig> m_ServerConfig{};
  struct sockaddr_in m_Address {
    0
  };
  int m_AddressLength{sizeof(m_Address)};
  bool m_IsErrored{false};
};

class RequestHandler;
class Response;
class Router;
class Route;
class Server {
public:
  Server();
  // Will Load SharedLibs from subdirectory
  // This will allow that VWeb will run as Standalone and will load .so files
  // without recompiling itself
  void LoadSharedLibs();
  void Start();
  void Join() { m_WorkerThread->join(); }
  void Stop() { m_IsExit = true; }
  Ref<Router> &GetRouter() { return m_Router; }
  Ref<ServerConfig> &GetServerConfig() { return m_ServerConfig; }
  void AddRoute(const std::string &path, const Ref<Route> &route);
  void RemoveRoute(const std::string &path);

protected:
  void Execute();
  void OutgoingExecute(epoll_event &event);
  void IncomingExecute(epoll_event &event);
  void HandleRequestReading(epoll_event &event);
  void CreateRequest(int sockID);

protected:
  Ref<Router> m_Router;
  Ref<ServerConfig> m_ServerConfig;
  Ref<RequestHandler> m_RequestHandler;
  SafeMap<int, Accept> m_RawRequest{60000};
  SafeMap<int, SendData> m_OutRequest{60000};
  Ref<std::thread> m_WorkerThread;
  Mutex m_Mutex;
  bool m_IsExit{false};

protected:
  friend RequestHandler;
};
#pragma region VWEB_ROUTING
using ms = std::chrono::duration<double, std::milli>;
struct SessionData {
  virtual ~SessionData() = default;
};
struct Session {
  std::string Id;
  long TTLSeconds = 1440; // 24 minutes 1440 seconds
  bool IsValid();
  void Update();
  void Remove(const std::string &key);
  bool Has(const std::string &key);
  Ref<SessionData> &operator[](const std::string &key) { return m_Data[key]; }

protected:
  std::chrono::time_point<std::chrono::system_clock, ms> m_LastCall =
      std::chrono::system_clock::now();
  std::unordered_map<std::string, Ref<SessionData>> m_Data;
};
enum class HttpStatusCode : int {
  Continue = 100,
  SwitchingProtocols = 101,
  Processing = 102,
  EarlyHints = 103,
  OK = 200,
  Created = 201,
  Accepted = 202,
  NonAuthoritativeInformation = 203,
  NoContent = 204,
  ResetContent = 205,
  PartialContent = 206,
  MultiStatus = 207,
  AlreadyReported = 208,
  IMUsed = 226,
  MultipleChoices = 300,
  MovedPermanently = 301,
  Found = 302,
  SeeOther = 303,
  NotModified = 304,
  UseProxy = 305,
  TemporaryRedirect = 307,
  PermanentRedirect = 308,
  BadRequest = 400,
  Unauthorized = 401,
  PaymentRequired = 402,
  Forbidden = 403,
  NotFound = 404,
  MethodNotAllowed = 405,
  NotAcceptable = 406,
  ProxyAuthenticationRequired = 407,
  RequestTimeout = 408,
  Conflict = 409,
  Gone = 410,
  LengthRequired = 411,
  PreconditionFailed = 412,
  PayloadTooLarge = 413,
  URITooLong = 414,
  UnsupportedMediaType = 415,
  RangeNotSatisfiable = 416,
  ExpectationFailed = 417,
  ImATeapot = 418,
  UnprocessableEntity = 422,
  Locked = 423,
  FailedDependency = 424,
  UpgradeRequired = 426,
  PreconditionRequired = 428,
  TooManyRequests = 429,
  RequestHeaderFieldsTooLarge = 431,
  UnavailableForLegalReasons = 451,
  InternalServerError = 500,
  NotImplemented = 501,
  BadGateway = 502,
  ServiceUnavailable = 503,
  GatewayTimeout = 504,
  HTTPVersionNotSupported = 505,
  VariantAlsoNegotiates = 506,
  InsufficientStorage = 507,
  LoopDetected = 508,
  NotExtended = 510,
  NetworkAuthenticationRequired = 511
};
enum class HttpMethod {
  GET = 0,
  HEAD,
  OPTIONS,
  POST,
  PUT,
  PATCH,
  DELETE,
  FALLBACK
};
enum class CookieSameSite { Invalid = 0, Strict, Lax, None };
struct Cookie {
  std::string Name;
  std::string Value;
  std::string Expires;
  int MaxAge{};
  std::string Domain;
  std::string Path;
  bool Secure{};
  bool HttpOnly{};
  CookieSameSite SameSite{CookieSameSite::Invalid};
  bool IsOld = false;
};
struct Cookies {
public:
  void Remove(const std::string &name);
  bool Has(const std::string &name) const;
  Cookie &Get(const std::string &name) { return Data[name]; };
  std::string TransformToHeader();
  void CreateCookieFromString(const std::string &data);
  void CreateOld(const std::string &name, const std::string &value);

  std::unordered_map<std::string, Cookie> Data;
  Cookie &operator[](const std::string &name) { return Data[name]; }

protected:
  static void CookieToString(std::stringstream &stream, Cookie &cookie);
};
struct ParameterValue {
  void Add(const std::string &item) { m_Data.push_back(item); }
  void Set(const std::string &item) {
    m_Data.clear();
    m_Data.push_back(item);
  }
  std::string &Get(int item) { return m_Data[item]; }
  std::string &GetFirst() { return Get(0); }
  size_t Size() { return m_Data.size(); }
  Vector<std::string> &Values() { return m_Data; }

protected:
  Vector<std::string> m_Data{};
};

struct Request {
public:
  int ID;
  std::string Body;
  HttpMethod Method{HttpMethod::GET};
  std::string URI;
  Ref<Session> SessionData;
  Ref<Cookies> CookieData;
  Cookie &GetCookie(const std::string &key) { return CookieData->Get(key); };
  ParameterValue &Parameter(const std::string &key) { return Parameters[key]; }
  bool HasParameter(const std::string &key) const {
    return Parameters.contains(key);
  }
  std::string &FirstOf(const std::string &key) {
    return Parameters[key].GetFirst();
  }
  ParameterValue &Header(const std::string &key) { return Parameters[key]; }
  std::unordered_map<std::string, ParameterValue> Parameters;
  std::unordered_map<std::string, ParameterValue> Headers;
  Vector<std::string> URLParameters;
};
class Response {
public:
  size_t Length{0};
  Ref<Cookies> CookieData{nullptr};
  Ref<Session> SessionData{nullptr};
  HttpStatusCode Status{HttpStatusCode::OK};
  HttpMethod Method{HttpMethod::GET};

public:
  std::string GetResponse();
  void SetHeader(const std::string &key, ParameterValue &value);
  void SetHeader(const std::string &key, const std::string &value);
  void AddHeaders(const std::string &key, const Vector<std::string> &values);
  void AddHeader(const std::string &key, const std::string &value);
  void SetType(const std::string &type);
  void AddContent(const std::string &data);
  void SetStatus(HttpStatusCode);
  void SetMethod(HttpMethod);
  void Reset();
  Response &operator<<(const std::string &data) {
    m_Content << data;
    return *this;
  }

protected:
  std::string TransformHeaders(const std::string &content);
  std::string m_Type{"text/html"};
  std::stringstream m_Content;
  std::unordered_map<std::string, ParameterValue> m_Headers;
};
class MiddleWareHandler;
class RequestHandler {
public:
  explicit RequestHandler(const Ref<SocketManager> &manager);
  void InitThreads(int count);
  void AddRequest(Ref<Request> &request);
  void Stop();
  void SetRouter(Ref<Router> &router);
  void AddSendResponse(SendData);
  Ref<MiddleWareHandler> &Middleware() { return m_MiddleWare; }

protected:
  Ref<Router> m_Router{nullptr};
  Ref<SocketManager> m_SocketManager{nullptr};
  Server *m_Server{nullptr};
  SafeQueue<Ref<Request>> m_Requests{};
  ThreadPool m_Pool{"RequestHandler"};
  Ref<MiddleWareHandler> m_MiddleWare{nullptr};
  friend Server;
};

class Route {
public:
  Route() = default;
  virtual ~Route() = default;
  Route(std::initializer_list<HttpMethod>);
  virtual bool Execute(const Request &request, Response &response);
  virtual bool Get(const Request &request, Response &response);
  virtual bool Post(const Request &request, Response &response);
  virtual bool Put(const Request &request, Response &response);
  virtual bool Patch(const Request &request, Response &response);
  virtual bool Delete(const Request &request, Response &response);
  bool Options(const Request &request, Response &response);
  virtual bool Fallback(const Request &request, Response &response);
  bool SupportsMethod(const Request &request);
  virtual bool IsAllowed(const Request &request);

protected:
  bool m_AllowAll{true};
  Vector<HttpMethod> m_AllowedMethods;
  friend Router;
};

class Router {
public:
  Router();
  void AddRoute(const std::string &name, const Ref<Route> &route);
  Ref<Route> &GetRoute(const std::string &name);
  void DeleteRoute(const std::string &name);

  Ref<Response> HandleRoute(Ref<Request> &request);
  Ref<Route> FindRoute(Ref<Request> &request);
  static void AddToArgs(Ref<Request> &request, Vector<std::string> &items);

protected:
  std::unordered_map<std::string, Ref<Route>> m_Routes;
};

struct MiddleWare {
  int Pos{0};
  virtual void PreHandle(Request &){};
  virtual void PostHandle(Request &, Response &){};
  virtual void Shutdown(Request &, Response &){};
  bool operator<(const MiddleWare *rhs) const { return Pos < rhs->Pos; }
};

class MiddleWareHandler {
public:
  void HandlePre(Ref<Request> &);
  void HandlePost(Ref<Request> &, Ref<Response> &);
  void Shutdown(Ref<Request> &, Ref<Response> &);

public:
  template <class T> Ref<MiddleWare> Get() { return GetById(typeid(T).name()); }
  template <class T> void Set(Ref<MiddleWare> &instance) {
    auto &type = typeid(T);
    if (type.before(typeid(MiddleWare)))
      SetById(type.name(), instance);
  }
  template <class T> Ref<MiddleWare> Create() {
    return SetById(typeid(T).name(), CreateRef<T>());
  }
  template <class T> void Remove() { RemoveById(typeid(T).name()); }

protected:
  Ref<MiddleWare> GetById(const char *id);
  Ref<MiddleWare> SetById(const char *id, const Ref<MiddleWare> &);
  void RemoveById(const char *id);
  std::map<std::string, Ref<MiddleWare>> m_MiddleWares;
};

#pragma endregion VWEB_ROUTING
#pragma endregion VWEB
} // namespace VWeb