#if HAVE_OPENSSL && HAVE_QUIC
#include "guard.h"
#ifndef OPENSSL_NO_QUIC
#include <env-inl.h>
#include <memory_tracker-inl.h>
#include <ngtcp2/ngtcp2_crypto.h>
#include <node_buffer.h>
#include <node_errors.h>
#include <node_sockaddr-inl.h>
#include "session.h"
#include "sessionticket.h"
#include "tlscontext.h"

namespace node {

using v8::ArrayBufferView;
using v8::Just;
using v8::Local;
using v8::Maybe;
using v8::MaybeLocal;
using v8::Nothing;
using v8::Object;
using v8::Value;
using v8::ValueDeserializer;
using v8::ValueSerializer;

namespace quic {

namespace {
SessionTicket::AppData::Source* GetAppDataSource(SSL* ssl) {
  auto& tls_session = TLSSession::From(ssl);
  return &tls_session.session().ticket_app_data_source();
}
}  // namespace

SessionTicket::SessionTicket(Store&& ticket, Store&& transport_params)
    : ticket_(std::move(ticket)),
      transport_params_(std::move(transport_params)) {}

Maybe<SessionTicket> SessionTicket::FromV8Value(Environment* env,
                                                Local<Value> value) {
  if (!value->IsArrayBufferView()) {
    THROW_ERR_INVALID_ARG_TYPE(env, "The ticket must be an ArrayBufferView.");
    return Nothing<SessionTicket>();
  }

  Store content;
  if (!Store::From(value.As<ArrayBufferView>()).To(&content)) {
    return Nothing<SessionTicket>();
  }
  ngtcp2_vec vec = content;

  ValueDeserializer des(env->isolate(), vec.base, vec.len);

  if (des.ReadHeader(env->context()).IsNothing()) {
    THROW_ERR_INVALID_ARG_VALUE(env, "The ticket format is invalid.");
    return Nothing<SessionTicket>();
  }

  Local<Value> ticket;
  Local<Value> transport_params;

  if (!des.ReadValue(env->context()).ToLocal(&ticket) ||
      !des.ReadValue(env->context()).ToLocal(&transport_params)) {
    return Nothing<SessionTicket>();
  }
  if (!ticket->IsArrayBufferView()) {
    THROW_ERR_INVALID_ARG_TYPE(env, "The ticket must be an ArrayBufferView");
    return Nothing<SessionTicket>();
  }
  if (!transport_params->IsArrayBufferView()) {
    THROW_ERR_INVALID_ARG_TYPE(
        env, "The transport parameters must be an ArrayBufferView");
    return Nothing<SessionTicket>();
  }

  Store ticket_store;
  Store transport_params_store;
  if (!Store::From(ticket.As<ArrayBufferView>()).To(&ticket_store)) {
    return Nothing<SessionTicket>();
  }
  if (!Store::From(transport_params.As<ArrayBufferView>())
           .To(&transport_params_store)) {
    return Nothing<SessionTicket>();
  }

  return Just(SessionTicket(std::move(ticket_store),
                            std::move(transport_params_store)));
}

MaybeLocal<Object> SessionTicket::encode(Environment* env) const {
  auto context = env->context();
  ValueSerializer ser(env->isolate());
  ser.WriteHeader();

  if (ser.WriteValue(context, ticket_.ToUint8Array(env)).IsNothing() ||
      ser.WriteValue(context, transport_params_.ToUint8Array(env))
          .IsNothing()) {
    return MaybeLocal<Object>();
  }

  auto result = ser.Release();

  return Buffer::New(env, reinterpret_cast<char*>(result.first), result.second);
}

const uv_buf_t SessionTicket::ticket() const {
  return ticket_;
}

const ngtcp2_vec SessionTicket::transport_params() const {
  return transport_params_;
}

void SessionTicket::MemoryInfo(MemoryTracker* tracker) const {
  tracker->TrackField("ticket", ticket_);
  tracker->TrackField("transport_params", transport_params_);
}

int SessionTicket::GenerateCallback(SSL* ssl, void* arg) {
  AppData::Collect(ssl);
  return 1;
}

SSL_TICKET_RETURN SessionTicket::DecryptedCallback(SSL* ssl,
                                                   SSL_SESSION* session,
                                                   const unsigned char* keyname,
                                                   size_t keyname_len,
                                                   SSL_TICKET_STATUS status,
                                                   void* arg) {
  switch (status) {
    default:
      return SSL_TICKET_RETURN_IGNORE;
    case SSL_TICKET_EMPTY:
      [[fallthrough]];
    case SSL_TICKET_NO_DECRYPT:
      return SSL_TICKET_RETURN_IGNORE_RENEW;
    case SSL_TICKET_SUCCESS_RENEW:
      return static_cast<SSL_TICKET_RETURN>(
          AppData::Extract(ssl, session, AppData::Source::Flag::STATUS_RENEW));
    case SSL_TICKET_SUCCESS:
      return static_cast<SSL_TICKET_RETURN>(AppData::Extract(ssl, session));
  }
}

SessionTicket::AppData::AppData(SSL* ssl, SSL_SESSION* session)
    : ssl_(ssl), session_(session) {}

SSL_SESSION* SessionTicket::AppData::GetSession() const {
  return session_ != nullptr ? session_ : SSL_get0_session(ssl_);
}

bool SessionTicket::AppData::Set(const uv_buf_t& data) {
  if (set_ || data.base == nullptr || data.len == 0) return false;
  set_ = true;
  SSL_SESSION_set1_ticket_appdata(GetSession(), data.base, data.len);
  return set_;
}

std::optional<const uv_buf_t> SessionTicket::AppData::Get() const {
  auto* sess = GetSession();
  if (sess == nullptr) return std::nullopt;
  uv_buf_t buf;
  int ret =
      SSL_SESSION_get0_ticket_appdata(sess,
                                      reinterpret_cast<void**>(&buf.base),
                                      reinterpret_cast<size_t*>(&buf.len));
  if (ret != 1) return std::nullopt;
  return buf;
}

void SessionTicket::AppData::Collect(SSL* ssl) {
  AppData app_data(ssl);
  if (auto source = GetAppDataSource(ssl)) {
    source->CollectSessionTicketAppData(&app_data);
  }
}

SessionTicket::AppData::Status SessionTicket::AppData::Extract(
    SSL* ssl, SSL_SESSION* session, Source::Flag flag) {
  auto source = GetAppDataSource(ssl);
  if (source != nullptr) {
    AppData app_data(ssl, session);
    return source->ExtractSessionTicketAppData(app_data, flag);
  }
  return Status::TICKET_IGNORE;
}

}  // namespace quic
}  // namespace node

#endif  // OPENSSL_NO_QUIC
#endif  // HAVE_OPENSSL && HAVE_QUIC
