#include "quiche/oblivious_http/oblivious_http_client.h"

#include <stdint.h>

#include <memory>
#include <string>
#include <utility>

#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/escaping.h"
#include "absl/strings/string_view.h"
#include "quiche/common/platform/api/quiche_test.h"
#include "quiche/common/platform/api/quiche_thread.h"

namespace quiche {

std::string GetHpkePrivateKey() {
  // Dev/Test private key generated using Keystore.
  absl::string_view hpke_key_hex =
      "b77431ecfa8f4cfc30d6e467aafa06944dffe28cb9dd1409e33a3045f5adc8a1";
  return absl::HexStringToBytes(hpke_key_hex);
}

std::string GetHpkePublicKey() {
  // Dev/Test public key generated using Keystore.
  absl::string_view public_key =
      "6d21cfe09fbea5122f9ebc2eb2a69fcc4f06408cd54aac934f012e76fcdcef62";
  return absl::HexStringToBytes(public_key);
}

const ObliviousHttpHeaderKeyConfig GetOhttpKeyConfig(uint8_t key_id,
                                                     uint16_t kem_id,
                                                     uint16_t kdf_id,
                                                     uint16_t aead_id) {
  auto ohttp_key_config =
      ObliviousHttpHeaderKeyConfig::Create(key_id, kem_id, kdf_id, aead_id);
  EXPECT_TRUE(ohttp_key_config.ok());
  return ohttp_key_config.value();
}

bssl::UniquePtr<EVP_HPKE_KEY> ConstructHpkeKey(
    absl::string_view hpke_key,
    const ObliviousHttpHeaderKeyConfig& ohttp_key_config) {
  bssl::UniquePtr<EVP_HPKE_KEY> bssl_hpke_key(EVP_HPKE_KEY_new());
  EXPECT_NE(bssl_hpke_key, nullptr);
  EXPECT_TRUE(EVP_HPKE_KEY_init(
      bssl_hpke_key.get(), ohttp_key_config.GetHpkeKem(),
      reinterpret_cast<const uint8_t*>(hpke_key.data()), hpke_key.size()));
  return bssl_hpke_key;
}

TEST(ObliviousHttpClient, TestEncapsulate) {
  auto client = ObliviousHttpClient::Create(
      GetHpkePublicKey(),
      GetOhttpKeyConfig(8, EVP_HPKE_DHKEM_X25519_HKDF_SHA256,
                        EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM));
  ASSERT_TRUE(client.ok());
  auto encrypted_req = client->CreateObliviousHttpRequest("test string 1");
  ASSERT_TRUE(encrypted_req.ok());
  auto serialized_encrypted_req = encrypted_req->EncapsulateAndSerialize();
  ASSERT_FALSE(serialized_encrypted_req.empty());
}

TEST(ObliviousHttpClient, TestEncryptingMultipleRequestsWithSingleInstance) {
  auto client = ObliviousHttpClient::Create(
      GetHpkePublicKey(),
      GetOhttpKeyConfig(1, EVP_HPKE_DHKEM_X25519_HKDF_SHA256,
                        EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM));
  ASSERT_TRUE(client.ok());
  auto ohttp_req_1 = client->CreateObliviousHttpRequest("test string 1");
  ASSERT_TRUE(ohttp_req_1.ok());
  auto serialized_ohttp_req_1 = ohttp_req_1->EncapsulateAndSerialize();
  ASSERT_FALSE(serialized_ohttp_req_1.empty());
  auto ohttp_req_2 = client->CreateObliviousHttpRequest("test string 2");
  ASSERT_TRUE(ohttp_req_2.ok());
  auto serialized_ohttp_req_2 = ohttp_req_2->EncapsulateAndSerialize();
  ASSERT_FALSE(serialized_ohttp_req_2.empty());
  EXPECT_NE(serialized_ohttp_req_1, serialized_ohttp_req_2);
}

TEST(ObliviousHttpClient, TestInvalidHPKEKey) {
  // Invalid public key.
  EXPECT_EQ(ObliviousHttpClient::Create(
                "Invalid HPKE key",
                GetOhttpKeyConfig(50, EVP_HPKE_DHKEM_X25519_HKDF_SHA256,
                                  EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM))
                .status()
                .code(),
            absl::StatusCode::kInvalidArgument);
  // Empty public key.
  EXPECT_EQ(ObliviousHttpClient::Create(
                /*hpke_public_key*/ "",
                GetOhttpKeyConfig(50, EVP_HPKE_DHKEM_X25519_HKDF_SHA256,
                                  EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM))
                .status()
                .code(),
            absl::StatusCode::kInvalidArgument);
}

TEST(ObliviousHttpClient,
     TestTwoSamePlaintextsWillGenerateDifferentEncryptedPayloads) {
  // Due to the nature of the encapsulated_key generated in HPKE being unique
  // for every request, expect different encrypted payloads when encrypting same
  // plaintexts.
  auto client = ObliviousHttpClient::Create(
      GetHpkePublicKey(),
      GetOhttpKeyConfig(1, EVP_HPKE_DHKEM_X25519_HKDF_SHA256,
                        EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM));
  ASSERT_TRUE(client.ok());
  auto encrypted_request_1 =
      client->CreateObliviousHttpRequest("same plaintext");
  ASSERT_TRUE(encrypted_request_1.ok());
  auto serialized_encrypted_request_1 =
      encrypted_request_1->EncapsulateAndSerialize();
  ASSERT_FALSE(serialized_encrypted_request_1.empty());
  auto encrypted_request_2 =
      client->CreateObliviousHttpRequest("same plaintext");
  ASSERT_TRUE(encrypted_request_2.ok());
  auto serialized_encrypted_request_2 =
      encrypted_request_2->EncapsulateAndSerialize();
  ASSERT_FALSE(serialized_encrypted_request_2.empty());
  EXPECT_NE(serialized_encrypted_request_1, serialized_encrypted_request_2);
}

TEST(ObliviousHttpClient, TestObliviousResponseHandling) {
  auto ohttp_key_config =
      GetOhttpKeyConfig(1, EVP_HPKE_DHKEM_X25519_HKDF_SHA256,
                        EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM);
  auto encapsulate_req_on_client =
      ObliviousHttpRequest::CreateClientObliviousRequest(
          "test", GetHpkePublicKey(), ohttp_key_config);
  ASSERT_TRUE(encapsulate_req_on_client.ok());
  auto decapsulate_req_on_gateway =
      ObliviousHttpRequest::CreateServerObliviousRequest(
          encapsulate_req_on_client->EncapsulateAndSerialize(),
          *(ConstructHpkeKey(GetHpkePrivateKey(), ohttp_key_config)),
          ohttp_key_config);
  ASSERT_TRUE(decapsulate_req_on_gateway.ok());
  auto gateway_request_context =
      std::move(decapsulate_req_on_gateway.value()).ReleaseContext();
  auto encapsulate_resp_on_gateway =
      ObliviousHttpResponse::CreateServerObliviousResponse(
          "test response", gateway_request_context);
  ASSERT_TRUE(encapsulate_resp_on_gateway.ok());

  auto client =
      ObliviousHttpClient::Create(GetHpkePublicKey(), ohttp_key_config);
  ASSERT_TRUE(client.ok());
  auto client_request_context =
      std::move(encapsulate_req_on_client.value()).ReleaseContext();
  auto decapsulate_resp_on_client = client->DecryptObliviousHttpResponse(
      encapsulate_resp_on_gateway->EncapsulateAndSerialize(),
      client_request_context);
  ASSERT_TRUE(decapsulate_resp_on_client.ok());
  EXPECT_EQ(decapsulate_resp_on_client->GetPlaintextData(), "test response");
}

TEST(ObliviousHttpClient,
     DecryptResponseReceivedByTheClientUsingServersObliviousContext) {
  auto ohttp_key_config =
      GetOhttpKeyConfig(1, EVP_HPKE_DHKEM_X25519_HKDF_SHA256,
                        EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM);
  auto encapsulate_req_on_client =
      ObliviousHttpRequest::CreateClientObliviousRequest(
          "test", GetHpkePublicKey(), ohttp_key_config);
  ASSERT_TRUE(encapsulate_req_on_client.ok());
  auto decapsulate_req_on_gateway =
      ObliviousHttpRequest::CreateServerObliviousRequest(
          encapsulate_req_on_client->EncapsulateAndSerialize(),
          *(ConstructHpkeKey(GetHpkePrivateKey(), ohttp_key_config)),
          ohttp_key_config);
  ASSERT_TRUE(decapsulate_req_on_gateway.ok());
  auto gateway_request_context =
      std::move(decapsulate_req_on_gateway.value()).ReleaseContext();
  auto encapsulate_resp_on_gateway =
      ObliviousHttpResponse::CreateServerObliviousResponse(
          "test response", gateway_request_context);
  ASSERT_TRUE(encapsulate_resp_on_gateway.ok());

  auto client =
      ObliviousHttpClient::Create(GetHpkePublicKey(), ohttp_key_config);
  ASSERT_TRUE(client.ok());
  auto decapsulate_resp_on_client = client->DecryptObliviousHttpResponse(
      encapsulate_resp_on_gateway->EncapsulateAndSerialize(),
      gateway_request_context);
  ASSERT_TRUE(decapsulate_resp_on_client.ok());
  EXPECT_EQ(decapsulate_resp_on_client->GetPlaintextData(), "test response");
}

TEST(ObliviousHttpClient, TestWithMultipleThreads) {
  class TestQuicheThread : public QuicheThread {
   public:
    TestQuicheThread(const ObliviousHttpClient& client,
                     std::string request_payload,
                     ObliviousHttpHeaderKeyConfig ohttp_key_config)
        : QuicheThread("client_thread"),
          client_(client),
          request_payload_(request_payload),
          ohttp_key_config_(ohttp_key_config) {}

   protected:
    void Run() override {
      auto encrypted_request =
          client_.CreateObliviousHttpRequest(request_payload_);
      ASSERT_TRUE(encrypted_request.ok());
      ASSERT_FALSE(encrypted_request->EncapsulateAndSerialize().empty());
      // Setup recipient and get encrypted response payload.
      auto decapsulate_req_on_gateway =
          ObliviousHttpRequest::CreateServerObliviousRequest(
              encrypted_request->EncapsulateAndSerialize(),
              *(ConstructHpkeKey(GetHpkePrivateKey(), ohttp_key_config_)),
              ohttp_key_config_);
      ASSERT_TRUE(decapsulate_req_on_gateway.ok());
      auto gateway_request_context =
          std::move(decapsulate_req_on_gateway.value()).ReleaseContext();
      auto encapsulate_resp_on_gateway =
          ObliviousHttpResponse::CreateServerObliviousResponse(
              "test response", gateway_request_context);
      ASSERT_TRUE(encapsulate_resp_on_gateway.ok());
      ASSERT_FALSE(
          encapsulate_resp_on_gateway->EncapsulateAndSerialize().empty());
      auto client_request_context =
          std::move(encrypted_request.value()).ReleaseContext();
      auto decrypted_response = client_.DecryptObliviousHttpResponse(
          encapsulate_resp_on_gateway->EncapsulateAndSerialize(),
          client_request_context);
      ASSERT_TRUE(decrypted_response.ok());
      ASSERT_FALSE(decrypted_response->GetPlaintextData().empty());
    }

   private:
    const ObliviousHttpClient& client_;
    std::string request_payload_;
    ObliviousHttpHeaderKeyConfig ohttp_key_config_;
  };

  auto ohttp_key_config =
      GetOhttpKeyConfig(1, EVP_HPKE_DHKEM_X25519_HKDF_SHA256,
                        EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM);
  auto client =
      ObliviousHttpClient::Create(GetHpkePublicKey(), ohttp_key_config);

  TestQuicheThread t1(*client, "test request 1", ohttp_key_config);
  TestQuicheThread t2(*client, "test request 2", ohttp_key_config);
  t1.Start();
  t2.Start();
  t1.Join();
  t2.Join();
}

}  // namespace quiche
