// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

#include "arrow/json/object_parser.h"
#include "arrow/json/object_writer.h"

#include "parquet/encryption/key_material.h"
#include "parquet/encryption/key_metadata.h"
#include "parquet/exception.h"

using ::arrow::json::internal::ObjectParser;
using ::arrow::json::internal::ObjectWriter;

namespace parquet::encryption {

constexpr const char KeyMaterial::kKeyMaterialTypeField[];
constexpr const char KeyMaterial::kKeyMaterialType1[];

constexpr const char KeyMaterial::kFooterKeyIdInFile[];
constexpr const char KeyMaterial::kColumnKeyIdInFilePrefix[];

constexpr const char KeyMaterial::kIsFooterKeyField[];
constexpr const char KeyMaterial::kDoubleWrappingField[];
constexpr const char KeyMaterial::kKmsInstanceIdField[];
constexpr const char KeyMaterial::kKmsInstanceUrlField[];
constexpr const char KeyMaterial::kMasterKeyIdField[];
constexpr const char KeyMaterial::kWrappedDataEncryptionKeyField[];
constexpr const char KeyMaterial::kKeyEncryptionKeyIdField[];
constexpr const char KeyMaterial::kWrappedKeyEncryptionKeyField[];

KeyMaterial::KeyMaterial(bool is_footer_key, const std::string& kms_instance_id,
                         const std::string& kms_instance_url,
                         const std::string& master_key_id, bool is_double_wrapped,
                         const std::string& kek_id,
                         const std::string& encoded_wrapped_kek,
                         const std::string& encoded_wrapped_dek)
    : is_footer_key_(is_footer_key),
      kms_instance_id_(kms_instance_id),
      kms_instance_url_(kms_instance_url),
      master_key_id_(master_key_id),
      is_double_wrapped_(is_double_wrapped),
      kek_id_(kek_id),
      encoded_wrapped_kek_(encoded_wrapped_kek),
      encoded_wrapped_dek_(encoded_wrapped_dek) {}

KeyMaterial KeyMaterial::Parse(const std::string& key_material_string) {
  ObjectParser json_parser;
  ::arrow::Status status = json_parser.Parse(key_material_string);
  if (!status.ok()) {
    throw ParquetException("Failed to parse key material " + key_material_string);
  }

  // External key material - extract "key material type", and make sure it is supported
  std::string key_material_type;
  PARQUET_ASSIGN_OR_THROW(key_material_type,
                          json_parser.GetString(kKeyMaterialTypeField));
  if (kKeyMaterialType1 != key_material_type) {
    throw ParquetException("Wrong key material type: " + key_material_type + " vs " +
                           kKeyMaterialType1);
  }
  // Parse other fields (common to internal and external key material)
  return Parse(&json_parser);
}

KeyMaterial KeyMaterial::Parse(const ObjectParser* key_material_json) {
  // 2. Check if "key material" belongs to file footer key
  bool is_footer_key;
  PARQUET_ASSIGN_OR_THROW(is_footer_key, key_material_json->GetBool(kIsFooterKeyField));
  std::string kms_instance_id;
  std::string kms_instance_url;
  if (is_footer_key) {
    // 3.  For footer key, extract KMS Instance ID
    PARQUET_ASSIGN_OR_THROW(kms_instance_id,
                            key_material_json->GetString(kKmsInstanceIdField));
    // 4.  For footer key, extract KMS Instance URL
    PARQUET_ASSIGN_OR_THROW(kms_instance_url,
                            key_material_json->GetString(kKmsInstanceUrlField));
  }
  // 5. Extract master key ID
  std::string master_key_id;
  PARQUET_ASSIGN_OR_THROW(master_key_id, key_material_json->GetString(kMasterKeyIdField));
  // 6. Extract wrapped DEK
  std::string encoded_wrapped_dek;
  PARQUET_ASSIGN_OR_THROW(encoded_wrapped_dek,
                          key_material_json->GetString(kWrappedDataEncryptionKeyField));
  std::string kek_id;
  std::string encoded_wrapped_kek;
  // 7. Check if "key material" was generated in double wrapping mode
  bool is_double_wrapped;
  PARQUET_ASSIGN_OR_THROW(is_double_wrapped,
                          key_material_json->GetBool(kDoubleWrappingField));
  if (is_double_wrapped) {
    // 8. In double wrapping mode, extract KEK ID
    PARQUET_ASSIGN_OR_THROW(kek_id,
                            key_material_json->GetString(kKeyEncryptionKeyIdField));
    // 9. In double wrapping mode, extract wrapped KEK
    PARQUET_ASSIGN_OR_THROW(encoded_wrapped_kek,
                            key_material_json->GetString(kWrappedKeyEncryptionKeyField));
  }

  return KeyMaterial(is_footer_key, kms_instance_id, kms_instance_url, master_key_id,
                     is_double_wrapped, kek_id, encoded_wrapped_kek, encoded_wrapped_dek);
}

std::string KeyMaterial::SerializeToJson(
    bool is_footer_key, const std::string& kms_instance_id,
    const std::string& kms_instance_url, const std::string& master_key_id,
    bool is_double_wrapped, const std::string& kek_id,
    const std::string& encoded_wrapped_kek, const std::string& encoded_wrapped_dek,
    bool is_internal_storage) {
  ObjectWriter json_writer;
  json_writer.SetString(kKeyMaterialTypeField, kKeyMaterialType1);

  if (is_internal_storage) {
    // 1. for internal storage, key material and key metadata are the same.
    // adding the "internalStorage" field that belongs to KeyMetadata.
    json_writer.SetBool(KeyMetadata::kKeyMaterialInternalStorageField, true);
  }
  // 2. Write isFooterKey
  json_writer.SetBool(kIsFooterKeyField, is_footer_key);
  if (is_footer_key) {
    // 3. For footer key, write KMS Instance ID
    json_writer.SetString(kKmsInstanceIdField, kms_instance_id);
    // 4. For footer key, write KMS Instance URL
    json_writer.SetString(kKmsInstanceUrlField, kms_instance_url);
  }
  // 5. Write master key ID
  json_writer.SetString(kMasterKeyIdField, master_key_id);
  // 6. Write wrapped DEK
  json_writer.SetString(kWrappedDataEncryptionKeyField, encoded_wrapped_dek);
  // 7. Write isDoubleWrapped
  json_writer.SetBool(kDoubleWrappingField, is_double_wrapped);
  if (is_double_wrapped) {
    // 8. In double wrapping mode, write KEK ID
    json_writer.SetString(kKeyEncryptionKeyIdField, kek_id);
    // 9. In double wrapping mode, write wrapped KEK
    json_writer.SetString(kWrappedKeyEncryptionKeyField, encoded_wrapped_kek);
  }

  return json_writer.Serialize();
}

}  // namespace parquet::encryption
