#include "Utils.h"
#include "Arc.hpp"

#include <Authentication.h>

namespace sharedjsi
{

DEFINE_RETAIN_RELEASE_FOR(Function, v_retain, v_release);
extern "C"
{
void v_auth_client_validity_update(void *ctx, int is_web_valid, int is_x509_valid)
{
  Arc<Function> const& borrowedJsCallback = Arc<Function>::borrow_from_raw(ctx);
  // copy ctor / retain so as to keep the ArcPointee<Function> alive until the enqueued_call completes.
  jsi_enqueue_call([=, jsCallback=Arc<Function>(borrowedJsCallback)](Runtime &runtime) {
    jsCallback->call(runtime,
                                   Value(static_cast<double>(is_web_valid)),
                                   Value(static_cast<double>(is_x509_valid)));
  });
}

void v_auth_client_expiring_callback(void *ctx, uint32_t secs_until_expiry)
{
  Arc<Function> const& borrowedJsCallback = Arc<Function>::borrow_from_raw(ctx);
  // copy ctor / retain so as to keep the ArcPointee<Function> alive until the enqueued_call completes.
  jsi_enqueue_call([=, jsCallback=Arc<Function>(borrowedJsCallback)](Runtime &runtime) {
    jsCallback->call(runtime,
                                   Value(static_cast<double>(secs_until_expiry)));
  });
}

void v_authentication_status_handler(void *ctx, dittoffi_authentication_status_t *ffi_auth_status)
{
  auto ptr = (void *)(ctx);
  Arc<Function> const& borrowedJsCallback = Arc<Function>::borrow_from_raw(ptr);
  // copy ctor / retain so as to keep the ArcPointee<Function> alive until the enqueued_call completes.
  Arc<Function> jsCallback /* = */ (borrowedJsCallback);
  jsi_enqueue_call([=](Runtime &runtime) {
    auto obj = cPointerToJSPointerObject<dittoffi_authentication_status_t>(runtime, ffi_auth_status);
    jsCallback->call(runtime, obj);
  });
}
}

Function ditto_auth_client_is_web_valid(Runtime &runtime)
{
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime,
                                                               STRINGIFIED_FUNC_NAME()),
                                          0,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value
                                          {
    return ::ditto_auth_client_is_web_valid(jsPointerObjectToCPointer<CDitto_t>(runtime, arguments[0]));
  });
}

Function ditto_auth_client_make_login_provider(Runtime &runtime)
{
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime,
                                                               STRINGIFIED_FUNC_NAME()),
                                          0,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value
                                          {
    Function expiringCallback = arguments[0].getObject(runtime).getFunction(runtime);
    Arc<Function> wrappedStateFfiWrapper(std::move(expiringCallback));

    CLoginProvider_t *loginProvider = ::ditto_auth_client_make_login_provider(Arc<Function>::as_raw(wrappedStateFfiWrapper),
                                                                              v_retain,
                                                                              v_release,
                                                                              v_auth_client_expiring_callback);

    return cPointerToJSPointerObject(runtime, loginProvider);
  });
}

Function ditto_auth_set_login_provider(Runtime &runtime)
{
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime,
                                                               STRINGIFIED_FUNC_NAME()),
                                          0,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value
                                          {
    auto *ditto = jsPointerObjectToCPointer<CDitto_t>(runtime, arguments[0]);
    auto *loginProvider = jsPointerObjectToCPointer<CLoginProvider_t>(runtime, arguments[1]);

    ::ditto_auth_set_login_provider(ditto, loginProvider);
    return {};
  });
}

Function ditto_auth_client_login_with_token(Runtime &runtime)
{
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime,
                                                               STRINGIFIED_FUNC_NAME()),
                                          0,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value
                                          {
    auto *ditto = jsPointerObjectToCPointer<CDitto_t>(runtime, arguments[0]);
    std::string token = jsTypedArrayToCString(runtime, arguments[1]);
    std::string provider = jsTypedArrayToCString(runtime, arguments[2]);

    return ::ditto_auth_client_login_with_token(ditto, token.c_str(), provider.c_str());
  });
}

Function ditto_auth_client_login_with_token_and_feedback(Runtime &runtime)
{
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime,
                                                               STRINGIFIED_FUNC_NAME()),
                                          0,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value
                                          {

    auto *ditto = jsPointerObjectToCPointer<CDitto_t>(runtime, arguments[0]);

    std::string token = jsTypedArrayToCString(runtime, arguments[1]);
    std::string provider = jsTypedArrayToCString(runtime, arguments[2]);

    BoxedCharPtrResult_t res = ::ditto_auth_client_login_with_token_and_feedback(ditto, token.c_str(), provider.c_str());
    Object obj(runtime);
    obj.setProperty(runtime, "status_code", static_cast<double>(res.status_code));
    obj.setProperty(runtime, "c_string", cPointerToJSPointerObject<char>(runtime, res.c_string));
    return obj;
  });
}

Function ditto_auth_client_logout(Runtime &runtime)
{
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime,
                                                               STRINGIFIED_FUNC_NAME()),
                                          0,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value
                                          {
    return ::ditto_auth_client_logout(jsPointerObjectToCPointer<CDitto_t>(runtime, arguments[0]));
  });
}

Function ditto_auth_client_user_id(Runtime &runtime)
{
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime,
                                                               STRINGIFIED_FUNC_NAME()),
                                          0,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value
                                          {
    char *res = ::ditto_auth_client_user_id(jsPointerObjectToCPointer<CDitto_t>(runtime, arguments[0]));
    return cPointerToJSPointerObject<char>(runtime, res);
  });
}

Function dittoffi_ditto_set_authentication_status_handler(Runtime &runtime)
{
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime,
                                                               STRINGIFIED_FUNC_NAME()),
                                          0,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value
                                          {
    auto *ditto = jsPointerObjectToCPointer<CDitto_t>(runtime, arguments[0]);

    Function jsCallback = arguments[1].getObject(runtime).getFunction(runtime);
    Arc<Function> wrappedStateFfiWrapper(std::move(jsCallback));

    ::dittoffi_ditto_set_authentication_status_handler(ditto, {
      .env_ptr = Arc<Function>::into_raw(std::move(wrappedStateFfiWrapper)),
      .call = v_authentication_status_handler,
      .free = v_release,
    });
    return {};
  });
}

Function dittoffi_authentication_status_is_authenticated(Runtime &runtime)
{
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime,
                                                               STRINGIFIED_FUNC_NAME()),
                                          0,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value
                                          {
    return ::dittoffi_authentication_status_is_authenticated(jsPointerObjectToCPointer<dittoffi_authentication_status_t>(runtime, arguments[0]));
  });
}

Function dittoffi_authentication_status_user_id(Runtime &runtime)
{
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime, STRINGIFIED_FUNC_NAME()),
                                          0,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value
                                          {

    char *res = ::dittoffi_authentication_status_user_id(jsPointerObjectToCPointer<dittoffi_authentication_status_t>(runtime, arguments[0]));
    return cPointerToJSPointerObject<char>(runtime, res);
  });
}

Function dittoffi_authentication_status_free(Runtime &runtime)
{
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime, STRINGIFIED_FUNC_NAME()),
                                          0,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value
                                          {
    ::dittoffi_authentication_status_free(jsPointerObjectToCPointer<dittoffi_authentication_status_t>(runtime, arguments[0]));
    return {};
  });
}
}
