#pragma once
#include <drogon/exports.h>
#include <drogon/HttpTypes.h>
#include <drogon/drogon_callbacks.h>
#include <drogon/HttpResponse.h>
#include <drogon/HttpRequest.h>
#include <trantor/utils/NonCopyable.h>
#include <trantor/net/EventLoop.h>
#include <cstddef>
#include <functional>
#include <memory>
#include <future>
#include "drogon/HttpBinder.h"
#ifdef __cpp_impl_coroutine
#include <drogon/utils/coroutine.h>
#endif
namespace drogon
{
class HttpClient;
using HttpClientPtr = std::shared_ptr<HttpClient>;
#ifdef __cpp_impl_coroutine
namespace internal
{
struct HttpRespAwaiter : public CallbackAwaiter<HttpResponsePtr>
{
    HttpRespAwaiter(HttpClient *client, HttpRequestPtr req, double timeout)
        : client_(client), req_(std::move(req)), timeout_(timeout)
    {
    }
    void await_suspend(std::coroutine_handle<> handle);
  private:
    HttpClient *client_;
    HttpRequestPtr req_;
    double timeout_;
};
}  
#endif
class DROGON_EXPORT HttpClient : public trantor::NonCopyable
{
  public:
    virtual void sendRequest(const HttpRequestPtr &req,
                             const HttpReqCallback &callback,
                             double timeout = 0) = 0;
    virtual void sendRequest(const HttpRequestPtr &req,
                             HttpReqCallback &&callback,
                             double timeout = 0) = 0;
    std::pair<ReqResult, HttpResponsePtr> sendRequest(const HttpRequestPtr &req,
                                                      double timeout = 0)
    {
        assert(!getLoop()->isInLoopThread() &&
               "Deadlock detected! Calling a sync API from the same loop as "
               "the HTTP client processes on will deadlock the event loop");
        std::promise<std::pair<ReqResult, HttpResponsePtr>> prom;
        auto f = prom.get_future();
        sendRequest(
            req,
            [&prom](ReqResult r, const HttpResponsePtr &resp) {
                prom.set_value({r, resp});
            },
            timeout);
        return f.get();
    }
#ifdef __cpp_impl_coroutine
    internal::HttpRespAwaiter sendRequestCoro(HttpRequestPtr req,
                                              double timeout = 0)
    {
        return internal::HttpRespAwaiter(this, std::move(req), timeout);
    }
#endif
    virtual void setSockOptCallback(std::function<void(int)> cb) = 0;
    virtual std::size_t requestsBufferSize() = 0;
    virtual void setPipeliningDepth(size_t depth) = 0;
    virtual void enableCookies(bool flag = true) = 0;
    virtual void addCookie(const std::string &key,
                           const std::string &value) = 0;
    virtual void addCookie(const Cookie &cookie) = 0;
    virtual void setUserAgent(const std::string &userAgent) = 0;
    static HttpClientPtr newHttpClient(const std::string &ip,
                                       uint16_t port,
                                       bool useSSL = false,
                                       trantor::EventLoop *loop = nullptr,
                                       bool useOldTLS = false,
                                       bool validateCert = true);
    virtual trantor::EventLoop *getLoop() = 0;
    virtual size_t bytesSent() const = 0;
    virtual size_t bytesReceived() const = 0;
    virtual std::string host() const = 0;
    std::string getHost() const
    {
        return host();
    }
    virtual uint16_t port() const = 0;
    uint16_t getPort() const
    {
        return port();
    }
    virtual bool secure() const = 0;
    bool onDefaultPort() const
    {
        if (secure())
            return port() == 443;
        return port() == 80;
    }
    virtual void setCertPath(const std::string &cert,
                             const std::string &key) = 0;
    virtual void addSSLConfigs(
        const std::vector<std::pair<std::string, std::string>>
            &sslConfCmds) = 0;
    static HttpClientPtr newHttpClient(const std::string &hostString,
                                       trantor::EventLoop *loop = nullptr,
                                       bool useOldTLS = false,
                                       bool validateCert = true);
    virtual ~HttpClient()
    {
    }
  protected:
    HttpClient() = default;
};
#ifdef __cpp_impl_coroutine
class HttpException : public std::exception
{
  public:
    HttpException() = delete;
    explicit HttpException(ReqResult res)
        : resultCode_(res), message_(to_string_view(res))
    {
    }
    const char *what() const noexcept override
    {
        return message_.data();
    }
    ReqResult code() const
    {
        return resultCode_;
    }
  private:
    ReqResult resultCode_;
    std::string_view message_;
};
inline void internal::HttpRespAwaiter::await_suspend(
    std::coroutine_handle<> handle)
{
    assert(client_ != nullptr);
    assert(req_ != nullptr);
    client_->sendRequest(
        req_,
        [handle, this](ReqResult result, const HttpResponsePtr &resp) {
            if (result == ReqResult::Ok)
                setValue(resp);
            else
                setException(std::make_exception_ptr(HttpException(result)));
            handle.resume();
        },
        timeout_);
}
#endif
}  