#ifndef __VEREIGN_GRPC_IDENTITY_API_HH
#define __VEREIGN_GRPC_IDENTITY_API_HH

#include <vereign/grpc/gen/identity_api.hh>

#include <vereign/grpc/error_code.hh>
#include <vereign/kvstore/errors.hh>
#include <vereign/client_library/common_types.pb.h>
#include <vereign/client_library/identity_types.pb.h>
#include <boost/core/ignore_unused.hpp>

namespace vereign::grpc {

/**
 * Implementation of the gRPC `vereign::client_library::IdentityAPI::Service` service.
 *
 * Inherits all the API implementations from the generated gen::IdentityAPI and adds some
 * additional implementations.
 *
 * IdentityAPI is a thin layer on top of the service::IdentityService.
 */
template <class VereignService>
class IdentityAPI final : public gen::IdentityAPI<VereignService> {
public:
  // API service name.
  static constexpr const char* Name = gen::IdentityAPI<VereignService>::Name;

  using VereignServiceType = VereignService;
  using VereignServicePtr = std::unique_ptr<VereignService>;

  /**
   * Constructs IdentityAPI instance.
   *
   * @param service The client library Identity service.
   */
  IdentityAPI(VereignServicePtr&& service)
    : gen::IdentityAPI<VereignService>{std::move(service)}
  {}

  // disable copying
  IdentityAPI(const IdentityAPI&) = delete;
  auto operator=(const IdentityAPI&) -> IdentityAPI& = delete;

  /**
   * Registers a new device.
   *
   * req.pin is required only under Linux.
   *
   * Under windows the system cypto storage is used.
   * When the device is registered a master key is created and the user will be asked for his
   * consent by showing a dialog window.
   *
   * Unexpected error codes:
   * - ErrorCode::ClientError Error that happen inside the Vereign Client Library
   * - ErrorCode::UnexpectedError Should never happen.
   *
   * Error codes of interest:
   * - ErrorCode::InvalidPinCode The pin code is invalid, currently during the registration an empty
   *    pin code is considered invalid.
   */
  auto LoginWithNewDevice(
    ::grpc::ServerContext* ctx,
    const client_library::LoginFormNewDevice* req,
    client_library::LoginFormNewDeviceResponse* resp
  ) -> ::grpc::Status override {
    boost::ignore_unused(ctx);

    try {
      this->service_->LoginWithNewDevice(req, resp);

    } catch (const kvstore::InvalidPinCodeError& e) {
      resp->set_code(ErrorCodeAsString(ErrorCode::InvalidPinCode));
      resp->set_status(ClientErrorStatus);
      resp->set_error(e.what());

    } catch (const std::exception& e) {
      resp->set_code(ErrorCodeAsString(ErrorCode::ClientError));
      resp->set_status(ClientErrorStatus);
      resp->set_error(e.what());

    } catch (...) {
      resp->set_code(ErrorCodeAsString(ErrorCode::UnexpectedError));
      resp->set_status(ClientErrorStatus);
      resp->set_error(ClientErrorStatus);
    }

    return ::grpc::Status::OK;
  }

  /**
   * Login with already registered device.
   *
   * req.pin is required only under Linux.
   *
   * Under windows the system cypto storage is used.
   * When the device is registered a master key is created and the user will be asked for his
   * consent by showing a dialog window.
   *
   * Unexpected error codes:
   * - ErrorCode::ClientError Error that happen inside the Vereign Client Library
   * - ErrorCode::UnexpectedError Should never happen.
   *
   * Error codes of interest:
   * - ErrorCode::DeviceNotRegistered The device is not registered.
   * - ErrorCode::InvalidPinCode The pin code is invalid and the crypto storage cannot be unlocked.
   * - ErrorCode::InvalidIdentity Under windows if for some reason the RSA master key has been changed.
   */
  auto LoginWithPreviouslyAddedDevice(
    ::grpc::ServerContext* ctx,
    const client_library::LoginFormPreviousAddedDevice* req,
    client_library::EmptyResponse* resp
  ) -> ::grpc::Status override {
    boost::ignore_unused(ctx);

    try {
      this->service_->LoginWithPreviouslyAddedDevice(req, resp);

    } catch (const kvstore::StorageNotInitializedError& e) {
      resp->set_code(ErrorCodeAsString(ErrorCode::DeviceNotRegistered));
      resp->set_status(ClientErrorStatus);
      resp->set_error(e.what());

    } catch (const kvstore::InvalidPinCodeError& e) {
      resp->set_code(ErrorCodeAsString(ErrorCode::InvalidPinCode));
      resp->set_status(ClientErrorStatus);
      resp->set_error(e.what());

    } catch (const kvstore::InvalidIdentityError& e) {
      resp->set_code(ErrorCodeAsString(ErrorCode::InvalidIdentity));
      resp->set_status(ClientErrorStatus);
      resp->set_error(e.what());

    } catch (const std::exception& e) {
      resp->set_code(ErrorCodeAsString(ErrorCode::ClientError));
      resp->set_status(ClientErrorStatus);
      resp->set_error(e.what());

    } catch (...) {
      resp->set_code(ErrorCodeAsString(ErrorCode::UnexpectedError));
      resp->set_status(ClientErrorStatus);
      resp->set_error(ClientErrorStatus);
    }

    return ::grpc::Status::OK;
  }
};

} // namespace vereign::grpc

#endif // __VEREIGN_GRPC_IDENTITY_API_HH