#include <vereign/service/identity_service.hh>

#include <vereign/client_library/common_types.pb.h>
#include <vereign/client_library/identity_types.pb.h>
#include <vereign/client_library/types.gen.pb.h>
#include <vereign/service/gen/passport_service.hh>
#include <vereign/restapi/client_session.hh>
#include <vereign/identity/provider.hh>
#include <vereign/test/device.hh>
#include <vereign/test/service_context.hh>
#include <vereign/core/temp.hh>
#include <vereign/core/fs.hh>

#include <util/env.hh>
#include <util/protobuf.hh>

#include <catch2/catch.hpp>

using namespace vereign;

TEST_CASE("service::IdentityService::LoginWithNewDevice", "[vereign/service][.integration]") {
  auto public_key = test::RequireEnv("TEST_VEREIGN_PUB_KEY");
  auto host = test::RequireEnv("TEST_VEREIGN_API_HOST");
  auto port = test::GetEnv("TEST_VEREIGN_API_PORT", "https");

  // the old device is used later for new device confirmation and authorization
  auto old_storage_path = core::TempFilePath("test_db_");
  auto rm_old_storage_path = core::RemoveFileGuard{old_storage_path};
  auto old_device_ctx = test::ServiceContext{host, port, old_storage_path};
  auto old_device = test::Device{old_device_ctx};
  old_device.Login(public_key);

  auto storage_path = core::TempFilePath("test_db_");
  auto rm_storage_path = core::RemoveFileGuard{storage_path};
  auto service_context = test::ServiceContext{host, port, storage_path};
  auto service = service::IdentityService{
    service_context.IdentityProvider(),
    service_context.ClientSession()
  };

  // register new device
  auto req = std::make_unique<vereign::client_library::LoginFormNewDevice>();
  auto resp = std::make_unique<vereign::client_library::LoginFormNewDeviceResponse>();
  req->set_pin("foo");

  service.LoginWithNewDevice(req.get(), resp.get());

  CHECK(resp->error() == "");
  CHECK(resp->status() == "OK");
  REQUIRE(resp->code() == "200");
  REQUIRE(resp->has_data() == true);

  // confirm and authorize the new device using an old device
  old_device.ConfirmNewDevice(resp->data().qrcode(), resp->data().actionid());
  old_device.AuthorizeDevice(service_context.IdentityProvider().GetDeviceHash());

  // list passports with the new device
  auto list_req = std::make_unique<vereign::client_library::ListPassportsForm>();
  auto list_resp = std::make_unique<vereign::client_library::ListPassportsFormResponse>();

  auto passport_service = service::gen::PassportService{service_context.ClientSession()};
  auto list_result = passport_service.ListPassports(list_req.get(), list_resp.get());

  auto result = list_result.get();

  // std::cout << vereign::test::ProtobufToJson(*result.Response) << std::endl;

  auto& list = result.Response;
  CHECK(list->error() == "");
  CHECK(list->status() == "OK");
  REQUIRE(list->code() == "200");
  CHECK(list->data().size() > 0);
  for (const auto& passport : list->data()) {
    CHECK(passport.uuid().size() == 36);
  }
}

TEST_CASE("service::IdentityService::LoginWithPreviouslyAddedDevice", "[vereign/service][.integration]") {
  auto public_key = test::RequireEnv("TEST_VEREIGN_PUB_KEY");
  auto host = test::RequireEnv("TEST_VEREIGN_API_HOST");
  auto port = test::GetEnv("TEST_VEREIGN_API_PORT", "https");

  auto storage_path = core::TempFilePath("test_db_");
  auto rm_storage_path = core::RemoveFileGuard{storage_path};

  // prepare new device
  auto old_storage_path = core::TempFilePath("test_db_");
  auto rm_old_storage_path = core::RemoveFileGuard{old_storage_path};
  auto old_device_ctx = test::ServiceContext{host, port, old_storage_path};
  auto old_device = test::Device{old_device_ctx};
  old_device.Login(public_key);
  auto old_service_context = test::ServiceContext{host, port, storage_path};
  old_device.CreateNewDevice(old_service_context, "pin");

  auto service_context = test::ServiceContext{host, port, storage_path};
  auto service = service::IdentityService{
    service_context.IdentityProvider(),
    service_context.ClientSession()
  };

  auto req = std::make_unique<vereign::client_library::LoginFormPreviousAddedDevice>();
  req->set_pin("pin");
  auto resp = std::make_unique<vereign::client_library::EmptyResponse>();

  service.LoginWithPreviouslyAddedDevice(req.get(), resp.get());

  CHECK(resp->error() == "");
  CHECK(resp->status() == "OK");
  REQUIRE(resp->code() == "200");

  // list passports with the logged device
  auto list_req = std::make_unique<vereign::client_library::ListPassportsForm>();
  auto list_resp = std::make_unique<vereign::client_library::ListPassportsFormResponse>();

  auto passport_service = service::gen::PassportService{service_context.ClientSession()};
  auto list_result = passport_service.ListPassports(list_req.get(), list_resp.get());

  auto result = list_result.get();

  // std::cout << vereign::test::ProtobufToJson(*result.Response) << std::endl;

  auto& list = result.Response;
  CHECK(list->error() == "");
  CHECK(list->status() == "OK");
  REQUIRE(list->code() == "200");
  CHECK(list->data().size() > 0);
  for (const auto& passport : list->data()) {
    CHECK(passport.uuid().size() == 36);
  }
}