#pragma once
#include <drogon/DrObject.h>
#include <drogon/drogon_callbacks.h>
#include <drogon/HttpRequest.h>
#include <drogon/HttpResponse.h>
#include <memory>
#ifdef __cpp_impl_coroutine
#include <drogon/utils/coroutine.h>
#endif
namespace drogon
{
class DROGON_EXPORT HttpMiddlewareBase : public virtual DrObjectBase
{
  public:
    virtual void invoke(const HttpRequestPtr &req,
                        MiddlewareNextCallback &&nextCb,
                        MiddlewareCallback &&mcb) = 0;
    ~HttpMiddlewareBase() override = default;
};
template <typename T, bool AutoCreation = true>
class HttpMiddleware : public DrObject<T>, public HttpMiddlewareBase
{
  public:
    static constexpr bool isAutoCreation{AutoCreation};
    ~HttpMiddleware() override = default;
};
namespace internal
{
DROGON_EXPORT void handleException(
    const std::exception &,
    const HttpRequestPtr &,
    std::function<void(const HttpResponsePtr &)> &&);
}
#ifdef __cpp_impl_coroutine
struct [[nodiscard]] MiddlewareNextAwaiter
    : public CallbackAwaiter<HttpResponsePtr>
{
  public:
    MiddlewareNextAwaiter(MiddlewareNextCallback &&nextCb)
        : nextCb_(std::move(nextCb))
    {
    }
    void await_suspend(std::coroutine_handle<> handle) noexcept
    {
        nextCb_([this, handle](const HttpResponsePtr &resp) {
            setValue(resp);
            handle.resume();
        });
    }
  private:
    MiddlewareNextCallback nextCb_;
};
template <typename T, bool AutoCreation = true>
class HttpCoroMiddleware : public DrObject<T>, public HttpMiddlewareBase
{
  public:
    static constexpr bool isAutoCreation{AutoCreation};
    ~HttpCoroMiddleware() override = default;
    void invoke(const HttpRequestPtr &req,
                MiddlewareNextCallback &&nextCb,
                MiddlewareCallback &&mcb) final
    {
        drogon::async_run([this,
                           req,
                           nextCb = std::move(nextCb),
                           mcb = std::move(mcb)]() mutable -> drogon::Task<> {
            HttpResponsePtr resp;
            try
            {
                resp = co_await invoke(req, {std::move(nextCb)});
            }
            catch (const std::exception &ex)
            {
                internal::handleException(ex, req, std::move(mcb));
                co_return;
            }
            catch (...)
            {
                LOG_ERROR << "Exception not derived from std::exception";
                co_return;
            }
            mcb(resp);
        });
    }
    virtual Task<HttpResponsePtr> invoke(const HttpRequestPtr &req,
                                         MiddlewareNextAwaiter &&next) = 0;
};
#endif
template <class Derived, bool AutoCreation = true>
class HttpOptionsMiddlewareImpl
    : public drogon::HttpMiddleware<Derived, AutoCreation>
{
  public:
    void invoke(const HttpRequestPtr &req,
                MiddlewareNextCallback &&nextCb,
                MiddlewareCallback &&mcb) override
    {
        if (req->method() == drogon::HttpMethod::Options)
            req->attributes()->insert("drogon.customCORShandling", true);
        nextCb(std::move(mcb));
    }
};
class HttpOptionsMiddlewareAuto
    : public HttpOptionsMiddlewareImpl<HttpOptionsMiddlewareAuto, true>
{
};
class HttpOptionsMiddleware
    : public HttpOptionsMiddlewareImpl<HttpOptionsMiddleware, false>
{
};
}  