/**
  @section license License

  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.
*/
#pragma once

#include <string>
#include <variant>
#include "tsutil/ts_bw_format.h"
#include <yaml-cpp/yaml.h>
#include <tscore/ink_uuid.h>

/// JSONRPC 2.0 Client API utility definitions. Only client applications should use these definitions. Internal handlers should not
/// use these definitions. Check @c mgmg2/rpc/jsonrpc/Defs.h instead

/// @brief @c JSONRPC 2.0 message mapping classes.
/// This is a very thin  API to deal with encoding/decoding jsonrpc 2.0 messages.
/// More info can be found https://www.jsonrpc.org/specification
namespace shared::rpc
{
struct JSONRPCRequest {
  std::string jsonrpc{"2.0"}; //!< Always 2.0 as this is the only version that the server supports.
  std::string method;         //!< remote method name.
  std::string id;             //!< optional, only needed for method calls.
  YAML::Node  params;         //!< This is defined by each remote API.

  virtual ~JSONRPCRequest() {} ///< Virtual destructor required because virtual methods present.

  virtual std::string
  get_method() const
  {
    return this->method;
  }
};

struct JSONRPCResponse {
  std::string id;      //!< Always 2.0 as this is the only version that the server supports.
  std::string jsonrpc; //!< Always 2.0
  YAML::Node  result; //!< Server's response, this could be decoded by using the YAML::convert mechanism. This depends solely on the
                      //!< server's data. Check docs and schemas.
  YAML::Node error;   //!<  Server's error.

  /// Handy function to check if the server sent any error
  bool
  is_error() const
  {
    return !error.IsNull();
  }

  YAML::Node fullMsg;
};

struct JSONRPCError {
  int32_t     code;    //!< High level error code.
  std::string message; //!< High level message
  // the following data is defined by TS, it will be a key/value pair.
  std::vector<std::pair<int32_t, std::string>> data;
  friend std::ostream                         &operator<<(std::ostream &os, const JSONRPCError &err);
};

/**
 All of the following definitions have the main purpose to have a object style idiom when dealing with request and responses from/to
 the JSONRPC server. This structures will then be used by the YAML codec implementation by using the YAML::convert style.
*/

///
/// @brief Base Client JSONRPC client request.
///
/// This represents a base class that implements the basic jsonrpc 2.0 required fields. We use UUID as an id generator
/// but this was an arbitrary choice, there is no conditions that forces to use this, any random id could work too.
/// When inherit from this class the @c id  and the @c jsonrpc fields which are constant in all the request will be automatically
/// generated.
// TODO: fix this as id is optional.
struct ClientRequest : JSONRPCRequest {
  using super = JSONRPCRequest;
  ClientRequest() { super::id = _idGen.getString(); }

private:
  struct IdGenerator {
    IdGenerator() { _uuid.initialize(TS_UUID_V4); }
    const char *
    getString()
    {
      return _uuid.valid() ? _uuid.getString() : "fix.this.is.not.an.id";
    }
    ATSUuid _uuid;
  };
  IdGenerator _idGen;
};

/// @brief Class definition just to make clear that it will be a notification and no ID will be set.
struct ClientRequestNotification : JSONRPCRequest {
  ClientRequestNotification() {}
};
/**
 * Specific JSONRPC request implementation should be placed here. All this definitions helps for readability and in particular
 * to easily emit json(or yaml) from this definitions.
 */

//------------------------------------------------------------------------------------------------------------------------------------

// handy definitions.
static const std::vector<int> CONFIG_REC_TYPES = {1, 16};
static const std::vector<int> METRIC_REC_TYPES = {2, 4, 32};
static constexpr bool         NOT_REGEX{false};
static constexpr bool         REGEX{true};

///
/// @brief Record lookup API helper class.
///
/// This utility class is used to encapsulate the basic data that contains a record lookup request.
/// Requests that are meant to interact with the admin_lookup_records API should inherit from this class if a special treatment is
/// needed. Otherwise use it directly.
///
struct RecordLookupRequest : ClientRequest {
  using super = ClientRequest;
  struct Params {
    std::string      recName;
    bool             isRegex{false};
    std::vector<int> recTypes;
  };
  std::string
  get_method() const override
  {
    return "admin_lookup_records";
  }
  template <typename... Args>
  void
  emplace_rec(Args &&...p)
  {
    super::params.push_back(Params{std::forward<Args>(p)...});
  }
};

struct RecordLookUpResponse {
  /// Response Records API  mapping utility classes.
  /// This utility class is used to hold the decoded response.
  struct RecordParamInfo {
    std::string name;
    int32_t     type;
    int32_t     version;
    bool        registered;
    int32_t     rsb;
    int32_t     order;
    int32_t     rclass;
    bool        overridable;
    std::string dataType;
    std::string currentValue;
    std::string defaultValue;

    struct ConfigMeta {
      int32_t     accessType;
      int32_t     updateStatus;
      int32_t     updateType;
      int32_t     checkType;
      int32_t     source;
      std::string checkExpr;
    };
    struct StatMeta {
      int32_t persistType;
    };
    std::variant<ConfigMeta, StatMeta> meta;
  };
  /// Record request error mapping class.
  struct RecordError {
    std::string          code;
    std::string          recordName;
    std::string          message; //!< optional.
    friend std::ostream &operator<<(std::ostream &os, const RecordError &re);
  };

  std::vector<RecordParamInfo> recordList;
  std::vector<RecordError>     errorList;
};

//------------------------------------------------------------------------------------------------------------------------------------

inline std::ostream &
operator<<(std::ostream &os, const RecordLookUpResponse::RecordError &re)
{
  std::string text;
  os << swoc::bwprint(text, "{:16s}: {}\n", "Record Name ", re.recordName);
  os << swoc::bwprint(text, "{:16s}: {}\n", "Code", re.code);
  if (!re.message.empty()) {
    os << swoc::bwprint(text, "{:16s}: {}\n", "Message", re.message);
  }
  return os;
}

inline std::ostream &
operator<<(std::ostream &os, const JSONRPCError &err)
{
  os << "Server Error found:\n";
  os << "[" << err.code << "] " << err.message << '\n';
  for (auto &&[code, message] : err.data) {
    os << "- [" << code << "] " << message << '\n';
  }

  return os;
}
} // namespace shared::rpc
