#pragma once
#include <drogon/exports.h>
#include <drogon/utils/Utilities.h>
#include <drogon/DrClassMap.h>
#include <drogon/HttpTypes.h>
#include <drogon/Session.h>
#include <drogon/Attribute.h>
#include <drogon/UploadFile.h>
#include <json/json.h>
#include <trantor/net/InetAddress.h>
#include <trantor/net/Certificate.h>
#include <trantor/utils/Date.h>
#include <memory>
#include <string>
#include <unordered_map>
#include <optional>
#include <string_view>
#include <trantor/net/TcpConnection.h>
namespace drogon
{
class HttpRequest;
using HttpRequestPtr = std::shared_ptr<HttpRequest>;
template <typename T>
T fromRequest(const HttpRequest &)
{
    LOG_ERROR << "You must specialize the fromRequest template for the type of "
              << DrClassMap::demangle(typeid(T).name());
    exit(1);
}
template <typename T>
HttpRequestPtr toRequest(T &&)
{
    LOG_ERROR << "You must specialize the toRequest template for the type of "
              << DrClassMap::demangle(typeid(T).name());
    exit(1);
}
template <>
HttpRequestPtr toRequest<const Json::Value &>(const Json::Value &pJson);
template <>
HttpRequestPtr toRequest(Json::Value &&pJson);
template <>
inline HttpRequestPtr toRequest<Json::Value &>(Json::Value &pJson)
{
    return toRequest((const Json::Value &)pJson);
}
template <>
std::shared_ptr<Json::Value> fromRequest(const HttpRequest &req);
class DROGON_EXPORT HttpRequest
{
  public:
    template <typename T>
    operator T() const
    {
        return fromRequest<T>(*this);
    }
    template <typename T>
    T as() const
    {
        return fromRequest<T>(*this);
    }
    virtual const char *methodString() const = 0;
    const char *getMethodString() const
    {
        return methodString();
    }
    virtual HttpMethod method() const = 0;
    HttpMethod getMethod() const
    {
        return method();
    }
    virtual bool isHead() const = 0;
    virtual const std::string &getHeader(std::string key) const = 0;
    virtual void addHeader(std::string field, const std::string &value) = 0;
    virtual void addHeader(std::string field, std::string &&value) = 0;
    virtual void removeHeader(std::string key) = 0;
    virtual const std::string &getCookie(const std::string &field) const = 0;
    virtual const SafeStringMap<std::string> &headers() const = 0;
    const SafeStringMap<std::string> &getHeaders() const
    {
        return headers();
    }
    virtual const SafeStringMap<std::string> &cookies() const = 0;
    const SafeStringMap<std::string> &getCookies() const
    {
        return cookies();
    }
    virtual size_t realContentLength() const = 0;
    size_t getRealContentLength() const
    {
        return realContentLength();
    }
    virtual const std::string &query() const = 0;
    const std::string &getQuery() const
    {
        return query();
    }
    std::string_view body() const
    {
        return std::string_view(bodyData(), bodyLength());
    }
    std::string_view getBody() const
    {
        return body();
    }
    virtual const char *bodyData() const = 0;
    virtual size_t bodyLength() const = 0;
    virtual void setBody(const std::string &body) = 0;
    virtual void setBody(std::string &&body) = 0;
    virtual const std::string &path() const = 0;
    virtual const std::string &getOriginalPath() const = 0;
    const std::string &getPath() const
    {
        return path();
    }
    std::string_view getMatchedPathPattern() const
    {
        return matchedPathPattern();
    }
    std::string_view matchedPathPattern() const
    {
        return std::string_view(matchedPathPatternData(),
                                matchedPathPatternLength());
    }
    virtual const std::vector<std::string> &getRoutingParameters() const = 0;
    virtual void setRoutingParameters(std::vector<std::string> &&params) = 0;
    virtual const char *matchedPathPatternData() const = 0;
    virtual size_t matchedPathPatternLength() const = 0;
    virtual const char *versionString() const = 0;
    const char *getVersionString() const
    {
        return versionString();
    }
    virtual Version version() const = 0;
    Version getVersion() const
    {
        return version();
    }
    virtual const SessionPtr &session() const = 0;
    const SessionPtr &getSession() const
    {
        return session();
    }
    virtual const AttributesPtr &attributes() const = 0;
    const AttributesPtr &getAttributes() const
    {
        return attributes();
    }
    virtual const SafeStringMap<std::string> &parameters() const = 0;
    const SafeStringMap<std::string> &getParameters() const
    {
        return parameters();
    }
    virtual const std::string &getParameter(const std::string &key) const = 0;
    template <typename T>
    std::optional<T> getOptionalParameter(const std::string &key)
    {
        auto &params = getParameters();
        auto it = params.find(key);
        if (it != params.end())
        {
            try
            {
                return std::optional<T>(
                    drogon::utils::fromString<T>(it->second));
            }
            catch (const std::exception &e)
            {
                LOG_ERROR << e.what();
                return std::optional<T>{};
            }
        }
        else
        {
            return std::optional<T>{};
        }
    }
    virtual const trantor::InetAddress &peerAddr() const = 0;
    const trantor::InetAddress &getPeerAddr() const
    {
        return peerAddr();
    }
    virtual const trantor::InetAddress &localAddr() const = 0;
    const trantor::InetAddress &getLocalAddr() const
    {
        return localAddr();
    }
    virtual const trantor::Date &creationDate() const = 0;
    const trantor::Date &getCreationDate() const
    {
        return creationDate();
    }
    virtual const trantor::CertificatePtr &peerCertificate() const = 0;
    const trantor::CertificatePtr &getPeerCertificate() const
    {
        return peerCertificate();
    }
    virtual const std::shared_ptr<Json::Value> &jsonObject() const = 0;
    const std::shared_ptr<Json::Value> &getJsonObject() const
    {
        return jsonObject();
    }
    virtual const std::string &getJsonError() const = 0;
    virtual ContentType contentType() const = 0;
    ContentType getContentType() const
    {
        return contentType();
    }
    virtual void setMethod(const HttpMethod method) = 0;
    virtual void setPath(const std::string &path) = 0;
    virtual void setPath(std::string &&path) = 0;
    virtual void setPathEncode(bool) = 0;
    virtual void setParameter(const std::string &key,
                              const std::string &value) = 0;
    virtual void setContentTypeCode(const ContentType type) = 0;
    void setContentTypeString(const std::string_view &typeString)
    {
        setContentTypeString(typeString.data(), typeString.size());
    }
    virtual void setCustomContentTypeString(const std::string &type) = 0;
    virtual void addCookie(std::string key, std::string value) = 0;
    virtual void setPassThrough(bool flag) = 0;
    static HttpRequestPtr newHttpRequest();
    static HttpRequestPtr newHttpJsonRequest(const Json::Value &data);
    static HttpRequestPtr newHttpFormPostRequest();
    static HttpRequestPtr newFileUploadRequest(
        const std::vector<UploadFile> &files);
    template <typename T>
    static HttpRequestPtr newCustomHttpRequest(T &&obj)
    {
        return toRequest(std::forward<T>(obj));
    }
    virtual bool isOnSecureConnection() const noexcept = 0;
    virtual void setContentTypeString(const char *typeString,
                                      size_t typeStringLength) = 0;
    virtual bool connected() const noexcept = 0;
    virtual const std::weak_ptr<trantor::TcpConnection> &getConnectionPtr()
        const noexcept = 0;
    virtual ~HttpRequest()
    {
    }
};
template <>
inline HttpRequestPtr toRequest<const Json::Value &>(const Json::Value &pJson)
{
    return HttpRequest::newHttpJsonRequest(pJson);
}
template <>
inline HttpRequestPtr toRequest(Json::Value &&pJson)
{
    return HttpRequest::newHttpJsonRequest(std::move(pJson));
}
template <>
inline std::shared_ptr<Json::Value> fromRequest(const HttpRequest &req)
{
    return req.getJsonObject();
}
}  