#pragma once
#include "addondata.hpp"
#include "instance.hpp"
#include "jstocbpp.hpp"
#include "transcoder.hpp"
#include <core/utils/movable_function.hxx>
#include <napi.h>

namespace couchnode
{

typedef couchbase::core::utils::movable_function<void(Napi::Env,
                                                      Napi::Function)>
    FwdFunc;

void jscbForward(Napi::Env env, Napi::Function callback, std::nullptr_t *,
                 FwdFunc *fn);
typedef Napi::TypedThreadSafeFunction<std::nullptr_t, FwdFunc, &jscbForward>
    CallCookieTTSF;

class CallCookie
{
public:
    CallCookie(Napi::Env env, Napi::Function jsCallback,
               const std::string &resourceName)
    {
        _ttsf =
            CallCookieTTSF::New(env, jsCallback, resourceName, 0, 1, nullptr);
        _ttsf.Ref(env);
    }

    CallCookie(CallCookie &o) = delete;

    CallCookie(CallCookie &&o)
        : _ttsf(std::move(o._ttsf))
    {
    }

    void invoke(FwdFunc &&callback)
    {
        _ttsf.BlockingCall(new FwdFunc(std::move(callback)));
        _ttsf.Release();
    }

private:
    CallCookieTTSF _ttsf;
};

class Connection : public Napi::ObjectWrap<Connection>
{
public:
    static Napi::FunctionReference &constructor(Napi::Env env)
    {
        return AddonData::fromEnv(env)->_connectionCtor;
    }

    static void Init(Napi::Env env, Napi::Object exports);

    Connection(const Napi::CallbackInfo &info);
    ~Connection();

    couchbase::core::cluster cluster() const
    {
        return _instance->_cluster;
    }

    Napi::Value jsConnect(const Napi::CallbackInfo &info);
    Napi::Value jsShutdown(const Napi::CallbackInfo &info);
    Napi::Value jsOpenBucket(const Napi::CallbackInfo &info);
    Napi::Value jsDiagnostics(const Napi::CallbackInfo &info);
    Napi::Value jsPing(const Napi::CallbackInfo &info);
    Napi::Value jsScan(const Napi::CallbackInfo &info);

    //#region Autogenerated Method Declarations

    Napi::Value jsPrepend(const Napi::CallbackInfo &info);
    Napi::Value jsPrependWithLegacyDurability(const Napi::CallbackInfo &info);
    Napi::Value jsExists(const Napi::CallbackInfo &info);
    Napi::Value jsHttpNoop(const Napi::CallbackInfo &info);
    Napi::Value jsUnlock(const Napi::CallbackInfo &info);
    Napi::Value jsGetAllReplicas(const Napi::CallbackInfo &info);
    Napi::Value jsUpsert(const Napi::CallbackInfo &info);
    Napi::Value jsUpsertWithLegacyDurability(const Napi::CallbackInfo &info);
    Napi::Value jsGetAnyReplica(const Napi::CallbackInfo &info);
    Napi::Value jsAppend(const Napi::CallbackInfo &info);
    Napi::Value jsAppendWithLegacyDurability(const Napi::CallbackInfo &info);
    Napi::Value jsQuery(const Napi::CallbackInfo &info);
    Napi::Value jsReplace(const Napi::CallbackInfo &info);
    Napi::Value jsReplaceWithLegacyDurability(const Napi::CallbackInfo &info);
    Napi::Value jsGetAndTouch(const Napi::CallbackInfo &info);
    Napi::Value jsRemove(const Napi::CallbackInfo &info);
    Napi::Value jsRemoveWithLegacyDurability(const Napi::CallbackInfo &info);
    Napi::Value jsGet(const Napi::CallbackInfo &info);
    Napi::Value jsLookupInAllReplicas(const Napi::CallbackInfo &info);
    Napi::Value jsAnalytics(const Napi::CallbackInfo &info);
    Napi::Value jsGetProjected(const Napi::CallbackInfo &info);
    Napi::Value jsDecrement(const Napi::CallbackInfo &info);
    Napi::Value jsDecrementWithLegacyDurability(const Napi::CallbackInfo &info);
    Napi::Value jsSearch(const Napi::CallbackInfo &info);
    Napi::Value jsTouch(const Napi::CallbackInfo &info);
    Napi::Value jsLookupIn(const Napi::CallbackInfo &info);
    Napi::Value jsDocumentView(const Napi::CallbackInfo &info);
    Napi::Value jsGetAndLock(const Napi::CallbackInfo &info);
    Napi::Value jsInsert(const Napi::CallbackInfo &info);
    Napi::Value jsInsertWithLegacyDurability(const Napi::CallbackInfo &info);
    Napi::Value jsLookupInAnyReplica(const Napi::CallbackInfo &info);
    Napi::Value jsMutateIn(const Napi::CallbackInfo &info);
    Napi::Value jsMutateInWithLegacyDurability(const Napi::CallbackInfo &info);
    Napi::Value jsIncrement(const Napi::CallbackInfo &info);
    Napi::Value jsIncrementWithLegacyDurability(const Napi::CallbackInfo &info);
    Napi::Value jsManagementGroupUpsert(const Napi::CallbackInfo &info);
    Napi::Value
    jsManagementEventingPauseFunction(const Napi::CallbackInfo &info);
    Napi::Value jsManagementQueryIndexGetAll(const Napi::CallbackInfo &info);
    Napi::Value jsManagementCollectionCreate(const Napi::CallbackInfo &info);
    Napi::Value
    jsManagementEventingResumeFunction(const Napi::CallbackInfo &info);
    Napi::Value jsManagementSearchIndexGetStats(const Napi::CallbackInfo &info);
    Napi::Value jsManagementBucketGetAll(const Napi::CallbackInfo &info);
    Napi::Value
    jsManagementQueryIndexBuildDeferred(const Napi::CallbackInfo &info);
    Napi::Value jsManagementClusterDescribe(const Napi::CallbackInfo &info);
    Napi::Value jsManagementSearchIndexGetAll(const Napi::CallbackInfo &info);
    Napi::Value
    jsManagementSearchIndexAnalyzeDocument(const Napi::CallbackInfo &info);
    Napi::Value jsManagementQueryIndexDrop(const Napi::CallbackInfo &info);
    Napi::Value
    jsManagementAnalyticsDatasetCreate(const Napi::CallbackInfo &info);
    Napi::Value jsManagementBucketFlush(const Napi::CallbackInfo &info);
    Napi::Value jsManagementAnalyticsIndexDrop(const Napi::CallbackInfo &info);
    Napi::Value jsManagementQueryIndexCreate(const Napi::CallbackInfo &info);
    Napi::Value jsManagementSearchIndexUpsert(const Napi::CallbackInfo &info);
    Napi::Value
    jsManagementAnalyticsDatasetGetAll(const Napi::CallbackInfo &info);
    Napi::Value
    jsManagementAnalyticsIndexGetAll(const Napi::CallbackInfo &info);
    Napi::Value
    jsManagementAnalyticsGetPendingMutations(const Napi::CallbackInfo &info);
    Napi::Value
    jsManagementAnalyticsDataverseDrop(const Napi::CallbackInfo &info);
    Napi::Value
    jsManagementAnalyticsLinkConnect(const Napi::CallbackInfo &info);
    Napi::Value
    jsManagementCollectionsManifestGet(const Napi::CallbackInfo &info);
    Napi::Value jsManagementChangePassword(const Napi::CallbackInfo &info);
    Napi::Value
    jsManagementClusterDeveloperPreviewEnable(const Napi::CallbackInfo &info);
    Napi::Value jsManagementAnalyticsLinkDrop(const Napi::CallbackInfo &info);
    Napi::Value jsManagementCollectionUpdate(const Napi::CallbackInfo &info);
    Napi::Value jsManagementBucketDescribe(const Napi::CallbackInfo &info);
    Napi::Value
    jsManagementEventingUpsertFunction(const Napi::CallbackInfo &info);
    Napi::Value jsManagementViewIndexGetAll(const Napi::CallbackInfo &info);
    Napi::Value jsManagementBucketGet(const Napi::CallbackInfo &info);
    Napi::Value jsManagementBucketUpdate(const Napi::CallbackInfo &info);
    Napi::Value jsManagementBucketDrop(const Napi::CallbackInfo &info);
    Napi::Value jsManagementFreeform(const Napi::CallbackInfo &info);
    Napi::Value jsManagementScopeDrop(const Napi::CallbackInfo &info);
    Napi::Value jsManagementViewIndexUpsert(const Napi::CallbackInfo &info);
    Napi::Value jsManagementUserGetAll(const Napi::CallbackInfo &info);
    Napi::Value jsManagementScopeCreate(const Napi::CallbackInfo &info);
    Napi::Value jsManagementEventingGetFunction(const Napi::CallbackInfo &info);
    Napi::Value jsManagementViewIndexDrop(const Napi::CallbackInfo &info);
    Napi::Value
    jsManagementAnalyticsLinkDisconnect(const Napi::CallbackInfo &info);
    Napi::Value jsManagementUserUpsert(const Napi::CallbackInfo &info);
    Napi::Value jsManagementEventingGetStatus(const Napi::CallbackInfo &info);
    Napi::Value
    jsManagementEventingGetAllFunctions(const Napi::CallbackInfo &info);
    Napi::Value
    jsManagementAnalyticsIndexCreate(const Napi::CallbackInfo &info);
    Napi::Value jsManagementScopeGetAll(const Napi::CallbackInfo &info);
    Napi::Value jsManagementUserGet(const Napi::CallbackInfo &info);
    Napi::Value jsManagementSearchIndexDrop(const Napi::CallbackInfo &info);
    Napi::Value
    jsManagementSearchIndexControlPlanFreeze(const Napi::CallbackInfo &info);
    Napi::Value jsManagementSearchGetStats(const Napi::CallbackInfo &info);
    Napi::Value jsManagementUserDrop(const Napi::CallbackInfo &info);
    Napi::Value
    jsManagementAnalyticsDataverseCreate(const Napi::CallbackInfo &info);
    Napi::Value
    jsManagementSearchIndexControlQuery(const Napi::CallbackInfo &info);
    Napi::Value jsManagementRoleGetAll(const Napi::CallbackInfo &info);
    Napi::Value jsManagementGroupGetAll(const Napi::CallbackInfo &info);
    Napi::Value
    jsManagementEventingDropFunction(const Napi::CallbackInfo &info);
    Napi::Value jsManagementCollectionDrop(const Napi::CallbackInfo &info);
    Napi::Value
    jsManagementSearchIndexControlIngest(const Napi::CallbackInfo &info);
    Napi::Value
    jsManagementEventingDeployFunction(const Napi::CallbackInfo &info);
    Napi::Value jsManagementGroupGet(const Napi::CallbackInfo &info);
    Napi::Value jsManagementViewIndexGet(const Napi::CallbackInfo &info);
    Napi::Value jsManagementBucketCreate(const Napi::CallbackInfo &info);
    Napi::Value
    jsManagementAnalyticsDatasetDrop(const Napi::CallbackInfo &info);
    Napi::Value jsManagementGroupDrop(const Napi::CallbackInfo &info);
    Napi::Value jsManagementSearchIndexGet(const Napi::CallbackInfo &info);
    Napi::Value
    jsManagementQueryIndexGetAllDeferred(const Napi::CallbackInfo &info);
    Napi::Value jsManagementQueryIndexBuild(const Napi::CallbackInfo &info);
    Napi::Value
    jsManagementEventingUndeployFunction(const Napi::CallbackInfo &info);
    Napi::Value
    jsManagementSearchIndexGetDocumentsCount(const Napi::CallbackInfo &info);
    Napi::Value jsManagementAnalyticsLinkGetAll(const Napi::CallbackInfo &info);

    //#endregion Autogenerated Method Declarations

private:
    template <typename Request, typename Handler>
    void executeOp(const std::string &opName, const Request &req,
                   Napi::Function jsCallback, Handler &&handler)
    {
        using response_type = typename Request::response_type;

        auto cookie = CallCookie(jsCallback.Env(), jsCallback, opName);
        this->_instance->_cluster.execute(
            req, [cookie = std::move(cookie),
                  handler = std::move(handler)](response_type resp) mutable {
                cookie.invoke(
                    [handler = std::move(handler), resp = std::move(resp)](
                        Napi::Env env, Napi::Function callback) mutable {
                        handler(env, callback, std::move(resp));
                    });
            });
    }

    template <typename Request>
    void executeOp(const std::string &opName, const Request &req,
                   Napi::Function jsCallback)
    {
        using response_type = typename Request::response_type;
        executeOp(opName, req, jsCallback,
                  [](Napi::Env env, Napi::Function callback,
                     response_type resp) mutable {
                      Napi::Value jsErr, jsRes;
                      try {
                          jsErr = cbpp_to_js(env, resp.ctx);
                          jsRes = cbpp_to_js(env, resp);
                      } catch (const Napi::Error &e) {
                          jsErr = e.Value();
                          jsRes = env.Null();
                      }

                      callback.Call({jsErr, jsRes});
                  });
    }

    template <typename Request>
    void executeOp(const std::string &opName, const Request &req,
                   Napi::Function jsCallback, Transcoder &&transcoder)
    {
        using response_type = typename Request::response_type;
        executeOp(opName, req, jsCallback,
                  [transcoder = std::move(transcoder)](
                      Napi::Env env, Napi::Function callback,
                      response_type resp) mutable {
                      Napi::Value jsErr, jsRes;
                      try {
                          jsErr = cbpp_to_js(env, resp.ctx);
                          jsRes = cbpp_to_js(env, resp, transcoder);
                      } catch (const Napi::Error &e) {
                          jsErr = e.Value();
                          jsRes = env.Null();
                      }

                      callback.Call({jsErr, jsRes});
                  });
    }

    Instance *_instance;
};

} // namespace couchnode
