// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

/**
 * @brief This sample provides the code implementation to use the Key Vault SDK client for C++
 * to list keys and versions of a given key, and list deleted keys in a soft-delete enabled Key
 * Vault.
 *
 * @remark The following environment variables must be set before running the sample.
 * - AZURE_KEYVAULT_URL:  To the Key Vault account URL.
 *
 */

#include <azure/core.hpp>
#include <azure/identity.hpp>
#include <azure/keyvault/keys.hpp>

#include <chrono>
#include <fstream>
#include <iostream>
#include <memory>
#include <thread>

using namespace Azure::Security::KeyVault::Keys;

int main()
{
  auto const keyVaultUrl = std::getenv("AZURE_KEYVAULT_URL");
  auto credential = std::make_shared<Azure::Identity::DefaultAzureCredential>();

  KeyClient keyClient(keyVaultUrl, credential);

  try
  {
    std::string rsaKeyName("CloudRsaKey-" + Azure::Core::Uuid::CreateUuid().ToString());
    auto rsaKey = CreateRsaKeyOptions(rsaKeyName);
    rsaKey.KeySize = 2048;
    rsaKey.ExpiresOn = std::chrono::system_clock::now() + std::chrono::hours(24 * 365);

    std::string ecKeyName("CloudEcKey-" + Azure::Core::Uuid::CreateUuid().ToString());
    auto ecKey = CreateEcKeyOptions(ecKeyName);
    ecKey.ExpiresOn = std::chrono::system_clock::now() + std::chrono::hours(24 * 365);

    std::cout << "\t-Create Keys" << std::endl;
    keyClient.CreateRsaKey(rsaKey);
    keyClient.CreateEcKey(ecKey);

    std::cout << "\t-List Keys" << std::endl;
    for (auto keys = keyClient.GetPropertiesOfKeys(); keys.HasPage(); keys.MoveToNextPage())
    {
      for (auto const& key : keys.Items)
      {
        if (key.Managed)
        {
          continue;
        }
        auto keyWithType = keyClient.GetKey(key.Name).Value;
        std::cout << "Key is returned with name: " << keyWithType.Name()
                  << " and type: " << keyWithType.GetKeyType().ToString() << std::endl;
      }
    }

    // update key
    CreateRsaKeyOptions newRsaKey(rsaKeyName);
    newRsaKey.KeySize = 4096;
    newRsaKey.ExpiresOn = std::chrono::system_clock::now() + std::chrono::hours(24 * 365);

    keyClient.CreateRsaKey(newRsaKey);

    // List key versions
    std::cout << "\t-List Key versions" << std::endl;
    for (auto keyVersions = keyClient.GetPropertiesOfKeyVersions(rsaKeyName); keyVersions.HasPage();
         keyVersions.MoveToNextPage())
    {
      for (auto const& key : keyVersions.Items)
      {
        std::cout << "Key's version: " << key.Version << " with name: " << key.Name << std::endl;
      }
    }

    std::cout << "\t-Delete Keys" << std::endl;
    DeleteKeyOperation rsaOperation = keyClient.StartDeleteKey(rsaKeyName);
    DeleteKeyOperation ecOperation = keyClient.StartDeleteKey(ecKeyName);

    // You only need to wait for completion if you want to purge or recover the key.
    rsaOperation.PollUntilDone(std::chrono::milliseconds(2000));
    ecOperation.PollUntilDone(std::chrono::milliseconds(2000));

    std::cout << "\t-List Deleted Keys" << std::endl;

    // Start getting the first page.
    for (auto keysDeletedPage = keyClient.GetDeletedKeys(); keysDeletedPage.HasPage();
         keysDeletedPage.MoveToNextPage())
    {
      for (auto const& key : keysDeletedPage.Items)
      {
        std::cout << "Deleted key's name: " << key.Name()
                  << ", recovery level: " << key.Properties.RecoveryLevel
                  << " and recovery Id: " << key.RecoveryId << std::endl;
      }
    }

    // If the Key Vault is soft-delete enabled, then for permanent deletion, deleted keys needs to
    // be purged.
    keyClient.PurgeDeletedKey(rsaKeyName);
    keyClient.PurgeDeletedKey(ecKeyName);
  }
  catch (Azure::Core::Credentials::AuthenticationException const& e)
  {
    std::cout << "Authentication Exception happened:" << std::endl << e.what() << std::endl;
    return 1;
  }
  catch (Azure::Core::RequestFailedException const& e)
  {
    std::cout << "Key Vault Client Exception happened:" << std::endl << e.Message << std::endl;
    return 1;
  }

  return 0;
}

template <class T>
static inline bool CompareNullableT(Azure::Nullable<T> const& left, Azure::Nullable<T> const& right)
{
  if (!left.HasValue() && !right.HasValue())
  {
    return true;
  }
  if (left.HasValue() && !right.HasValue())
  {
    return false;
  }
  if (!left.HasValue() && right.HasValue())
  {
    return false;
  }
  return left.Value() == right.Value();
}
