#ifndef QUICHE_OBLIVIOUS_HTTP_COMMON_OBLIVIOUS_HTTP_HEADER_KEY_CONFIG_H_
#define QUICHE_OBLIVIOUS_HTTP_COMMON_OBLIVIOUS_HTTP_HEADER_KEY_CONFIG_H_

#include <stdint.h>

#include "absl/container/btree_map.h"
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "openssl/base.h"
#include "quiche/common/platform/api/quiche_export.h"
#include "quiche/common/quiche_data_reader.h"

namespace quiche {

class QUICHE_EXPORT ObliviousHttpHeaderKeyConfig {
 public:
  // https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#section-4.1-4.2
  static constexpr absl::string_view kOhttpRequestLabel =
      "message/bhttp request";
  static constexpr absl::string_view kOhttpResponseLabel =
      "message/bhttp response";
  // Length of the Oblivious HTTP header.
  static constexpr uint32_t kHeaderLength =
      sizeof(uint8_t) + (3 * sizeof(uint16_t));
  static constexpr absl::string_view kKeyHkdfInfo = "key";
  static constexpr absl::string_view kNonceHkdfInfo = "nonce";

  static absl::StatusOr<ObliviousHttpHeaderKeyConfig> Create(uint8_t key_id,
                                                             uint16_t kem_id,
                                                             uint16_t kdf_id,
                                                             uint16_t aead_id);

  // Copyable to support stack allocated pass-by-value for trivial data members.
  ObliviousHttpHeaderKeyConfig(const ObliviousHttpHeaderKeyConfig& other) =
      default;
  ObliviousHttpHeaderKeyConfig& operator=(
      const ObliviousHttpHeaderKeyConfig& other) = default;

  // Movable.
  ObliviousHttpHeaderKeyConfig(ObliviousHttpHeaderKeyConfig&& other) = default;
  ObliviousHttpHeaderKeyConfig& operator=(
      ObliviousHttpHeaderKeyConfig&& other) = default;

  ~ObliviousHttpHeaderKeyConfig() = default;

  const EVP_HPKE_KEM* GetHpkeKem() const;
  const EVP_HPKE_KDF* GetHpkeKdf() const;
  const EVP_HPKE_AEAD* GetHpkeAead() const;

  uint8_t GetKeyId() const { return key_id_; }
  uint16_t GetHpkeKemId() const { return kem_id_; }
  uint16_t GetHpkeKdfId() const { return kdf_id_; }
  uint16_t GetHpkeAeadId() const { return aead_id_; }

  // Build HPKE context info [request_label, 0x00, keyID(1 byte),
  // kemID(2 bytes), kdfID(2 bytes), aeadID(2 bytes)] in network byte order and
  // return a sequence of bytes(bytestring).
  // https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#section-4.1-10
  std::string SerializeRecipientContextInfo(
      absl::string_view request_label =
          ObliviousHttpHeaderKeyConfig::kOhttpRequestLabel) const;

  // Parses the below Header
  // [keyID(1 byte), kemID(2 bytes), kdfID(2 bytes), aeadID(2 bytes)]
  // from the payload received in Ohttp Request, and verifies that these values
  // match with the info stored in `this` namely [key_id_, kem_id_, kdf_id_,
  // aead_id_]
  // https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#section-4.1-7
  absl::Status ParseOhttpPayloadHeader(absl::string_view payload_bytes) const;

  // Parses the Oblivious HTTP header [keyID(1 byte), kemID(2 bytes), kdfID(2
  // bytes), aeadID(2 bytes)] from the buffer initialized within
  // `QuicheDataReader`, and  verifies these values against instantiated class
  // data namely [key_id_, kem_id_, kdf_id_, aead_id_] for a match. On
  // success(i.e., if matched successfully), leaves `reader` pointing at the
  // first byte after the header.
  absl::Status ParseOhttpPayloadHeader(QuicheDataReader& reader) const;

  // Extracts Key ID from the OHTTP Request payload.
  static absl::StatusOr<uint8_t> ParseKeyIdFromObliviousHttpRequestPayload(
      absl::string_view payload_bytes);

  // Build Request header according to network byte order and return string.
  std::string SerializeOhttpPayloadHeader() const;

 private:
  // Constructor
  explicit ObliviousHttpHeaderKeyConfig(uint8_t key_id, uint16_t kem_id,
                                        uint16_t kdf_id, uint16_t aead_id);

  // Helps validate Key configuration for supported schemes.
  absl::Status ValidateKeyConfig() const;

  // Public Key configuration hosted by Gateway to facilitate Oblivious HTTP
  // HPKE encryption.
  // https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#name-key-configuration-encoding
  uint8_t key_id_;
  uint16_t kem_id_;
  uint16_t kdf_id_;
  uint16_t aead_id_;
};

// Contains multiple ObliviousHttpHeaderKeyConfig objects and associated private
// keys.  An ObliviousHttpHeaderKeyConfigs object can be constructed from the
// "Key Configuration" defined in the Oblivious HTTP spec.  Multiple key
// configurations maybe be supported by the server.
//
// See https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-04.html#section-3
// for details of the "Key Configuration" spec.
//
// ObliviousHttpKeyConfigs objects are immutable after construction.
class QUICHE_EXPORT ObliviousHttpKeyConfigs {
 public:
  // Below two structures follow the Single key configuration spec in OHTTP RFC.
  // https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-06.html#name-a-single-key-configuration
  struct SymmetricAlgorithmsConfig {
    uint16_t kdf_id;
    uint16_t aead_id;

    bool operator==(const SymmetricAlgorithmsConfig& other) const {
      return kdf_id == other.kdf_id && aead_id == other.aead_id;
    }

    template <typename H>
    friend H AbslHashValue(H h, const SymmetricAlgorithmsConfig& sym_alg_cfg) {
      return H::combine(std::move(h), sym_alg_cfg.kdf_id, sym_alg_cfg.aead_id);
    }
  };

  struct OhttpKeyConfig {
    uint8_t key_id;
    uint16_t kem_id;
    std::string public_key;  // Raw byte string.
    absl::flat_hash_set<SymmetricAlgorithmsConfig> symmetric_algorithms;

    bool operator==(const OhttpKeyConfig& other) const {
      return key_id == other.key_id && kem_id == other.kem_id &&
             public_key == other.public_key &&
             symmetric_algorithms == other.symmetric_algorithms;
    }

    template <typename H>
    friend H AbslHashValue(H h, const OhttpKeyConfig& ohttp_key_cfg) {
      return H::combine(std::move(h), ohttp_key_cfg.key_id,
                        ohttp_key_cfg.kem_id, ohttp_key_cfg.public_key,
                        ohttp_key_cfg.symmetric_algorithms);
    }
  };

  // Parses the "application/ohttp-keys" media type, which is a byte string
  // formatted according to the spec:
  // https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-04.html#section-3
  static absl::StatusOr<ObliviousHttpKeyConfigs> ParseConcatenatedKeys(
      absl::string_view key_configs);

  // Builds `ObliviousHttpKeyConfigs` with multiple key configurations, each
  // made up of Single Key Configuration([{key_id, kem_id, public key},
  // Set<SymmetricAlgos>]) encoding specified in section 3.
  // https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#name-key-configuration-encoding
  // @params: Set<{key_id, kem_id, public key, Set<HPKE Symmetric Algorithms>>.
  // @return: When given all valid configs supported by BoringSSL, builds and
  // returns `ObliviousHttpKeyConfigs`. If any one of the input configs are
  // invalid or unsupported by BSSL, returns an error.
  // @note: Subsequently, To get concatenated keys[contiguous byte string of
  // keys], use `GenerateConcatenatedKeys()`. This output can inturn be parsed
  // by `ObliviousHttpKeyConfigs::ParseConcatenatedKeys` on client side.
  static absl::StatusOr<ObliviousHttpKeyConfigs> Create(
      absl::flat_hash_set<OhttpKeyConfig> ohttp_key_configs);

  // Builds `ObliviousHttpKeyConfigs` with given public_key and Single key
  // configuration specified in `ObliviousHttpHeaderKeyConfig` object. After
  // successful `Create`, clients can call `GenerateConcatenatedKeys()` to build
  // the Single key config.
  static absl::StatusOr<ObliviousHttpKeyConfigs> Create(
      const ObliviousHttpHeaderKeyConfig& single_key_config,
      absl::string_view public_key);

  // Generates byte string corresponding to "application/ohttp-keys" media type.
  // https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-04.html#section-3
  absl::StatusOr<std::string> GenerateConcatenatedKeys() const;

  int NumKeys() const { return public_keys_.size(); }

  // Returns a preferred config to use.  The preferred key is the key with
  // the highest key_id.  If more than one configuration exists for the
  // preferred key any configuration may be returned.
  //
  // These methods are useful in the (common) case where only one key
  // configuration is supported by the server.
  ObliviousHttpHeaderKeyConfig PreferredConfig() const;

  absl::StatusOr<absl::string_view> GetPublicKeyForId(uint8_t key_id) const;

  // TODO(kmg): Add methods to somehow access other non-preferred key
  // configurations.

 private:
  using PublicKeyMap = absl::flat_hash_map<uint8_t, std::string>;
  using ConfigMap =
      absl::btree_map<uint8_t, std::vector<ObliviousHttpHeaderKeyConfig>,
                      std::greater<uint8_t>>;

  ObliviousHttpKeyConfigs(ConfigMap cm, PublicKeyMap km)
      : configs_(std::move(cm)), public_keys_(std::move(km)) {}

  static absl::Status ReadSingleKeyConfig(QuicheDataReader& reader,
                                          ConfigMap& configs,
                                          PublicKeyMap& keys);

  // A mapping from key_id to ObliviousHttpHeaderKeyConfig objects for that key.
  const ConfigMap configs_;

  // A mapping from key_id to the public key for that key_id.
  const PublicKeyMap public_keys_;
};

}  // namespace quiche

#endif  // QUICHE_OBLIVIOUS_HTTP_COMMON_OBLIVIOUS_HTTP_HEADER_KEY_CONFIG_H_
