//
// Copyright 2022 gRPC authors.
//
// Licensed 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 <grpc/support/port_platform.h>

#include "src/core/ext/xds/xds_cluster_specifier_plugin.h"

#include <stddef.h>

#include <map>
#include <utility>

#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/types/variant.h"
#include "upb/json_encode.h"
#include "upb/status.h"
#include "upb/upb.hpp"

#include <grpc/support/log.h>

#include "src/core/lib/json/json.h"
#include "src/proto/grpc/lookup/v1/rls_config.upb.h"
#include "src/proto/grpc/lookup/v1/rls_config.upbdefs.h"

namespace grpc_core {

//
// XdsRouteLookupClusterSpecifierPlugin
//

absl::string_view XdsRouteLookupClusterSpecifierPlugin::ConfigProtoName()
    const {
  return "grpc.lookup.v1.RouteLookupClusterSpecifier";
}

void XdsRouteLookupClusterSpecifierPlugin::PopulateSymtab(
    upb_DefPool* symtab) const {
  grpc_lookup_v1_RouteLookupConfig_getmsgdef(symtab);
}

Json XdsRouteLookupClusterSpecifierPlugin::GenerateLoadBalancingPolicyConfig(
    XdsExtension extension, upb_Arena* arena, upb_DefPool* symtab,
    ValidationErrors* errors) const {
  absl::string_view* serialized_plugin_config =
      absl::get_if<absl::string_view>(&extension.value);
  if (serialized_plugin_config == nullptr) {
    errors->AddError("could not parse plugin config");
    return {};
  }
  const auto* specifier = grpc_lookup_v1_RouteLookupClusterSpecifier_parse(
      serialized_plugin_config->data(), serialized_plugin_config->size(),
      arena);
  if (specifier == nullptr) {
    errors->AddError("could not parse plugin config");
    return {};
  }
  const auto* plugin_config =
      grpc_lookup_v1_RouteLookupClusterSpecifier_route_lookup_config(specifier);
  if (plugin_config == nullptr) {
    ValidationErrors::ScopedField field(errors, ".route_lookup_config");
    errors->AddError("field not present");
    return {};
  }
  upb::Status status;
  const upb_MessageDef* msg_type =
      grpc_lookup_v1_RouteLookupConfig_getmsgdef(symtab);
  size_t json_size = upb_JsonEncode(plugin_config, msg_type, symtab, 0, nullptr,
                                    0, status.ptr());
  if (json_size == static_cast<size_t>(-1)) {
    errors->AddError(absl::StrCat("failed to dump proto to JSON: ",
                                  upb_Status_ErrorMessage(status.ptr())));
    return {};
  }
  void* buf = upb_Arena_Malloc(arena, json_size + 1);
  upb_JsonEncode(plugin_config, msg_type, symtab, 0,
                 reinterpret_cast<char*>(buf), json_size + 1, status.ptr());
  auto json = Json::Parse(reinterpret_cast<char*>(buf));
  GPR_ASSERT(json.ok());
  return Json::Array{Json::Object{
      {"rls_experimental",
       Json::Object{
           {"routeLookupConfig", std::move(*json)},
           {"childPolicy",
            Json::Array{Json::Object{{"cds_experimental", Json::Object()}}}},
           {"childPolicyConfigTargetFieldName", "cluster"},
       }}}};
}

//
// XdsClusterSpecifierPluginRegistry
//

XdsClusterSpecifierPluginRegistry::XdsClusterSpecifierPluginRegistry() {
  RegisterPlugin(std::make_unique<XdsRouteLookupClusterSpecifierPlugin>());
}

void XdsClusterSpecifierPluginRegistry::RegisterPlugin(
    std::unique_ptr<XdsClusterSpecifierPluginImpl> plugin) {
  absl::string_view name = plugin->ConfigProtoName();
  registry_[name] = std::move(plugin);
}

const XdsClusterSpecifierPluginImpl*
XdsClusterSpecifierPluginRegistry::GetPluginForType(
    absl::string_view config_proto_type_name) const {
  auto it = registry_.find(config_proto_type_name);
  if (it == registry_.end()) return nullptr;
  return it->second.get();
}

void XdsClusterSpecifierPluginRegistry::PopulateSymtab(
    upb_DefPool* symtab) const {
  for (const auto& p : registry_) {
    p.second->PopulateSymtab(symtab);
  }
}

}  // namespace grpc_core
