
#include <cstring>
#include <string>
#include <string_view>

#include "google/protobuf/descriptor.pb.h"
#include "google/protobuf/descriptor.upb.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "absl/log/absl_check.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
#include "google/protobuf/unittest.upbdefs.h"
#include "upb/base/status.hpp"
#include "upb/mem/arena.hpp"
#include "upb/reflection/def.h"
#include "upb/reflection/def.hpp"
#include "upb/reflection/internal/def_pool.h"
#include "upb/test/parse_text_proto.h"

namespace upb_test {
namespace {

using ::testing::HasSubstr;
using ::testing::NotNull;

google_protobuf_FileDescriptorProto* ToUpbDescriptorSet(
    const google::protobuf::FileDescriptorProto& proto, upb::Arena& arena) {
  std::string serialized;
  (void)proto.SerializeToString(&serialized);
  return google_protobuf_FileDescriptorProto_parse(serialized.data(), serialized.size(),
                                          arena.ptr());
}

absl::StatusOr<upb::DefPool> LoadDescriptorSetFromProto(
    const google::protobuf::FileDescriptorSet& set) {
  upb::Arena arena;
  upb::DefPool defpool;
  upb::Status status;
  for (const auto& file : set.file()) {
    google_protobuf_FileDescriptorProto* upb_proto = ToUpbDescriptorSet(file, arena);
    ABSL_CHECK(upb_proto);
    upb::FileDefPtr file_def = defpool.AddFile(upb_proto, &status);
    if (!file_def) return absl::InternalError(status.error_message());
  }
  return defpool;
}

absl::StatusOr<upb::DefPool> LoadDescriptorProto(absl::string_view proto_text) {
  google::protobuf::FileDescriptorProto proto = ParseTextProtoOrDie(proto_text);
  google::protobuf::FileDescriptorSet set;
  *set.add_file() = proto;
  return LoadDescriptorSetFromProto(set);
}

TEST(ReflectionTest, OpenEnumWithNonZeroDefault) {
  absl::Status status = LoadDescriptorProto(
                            R"pb(
                              syntax: "proto3"
                              name: "F"
                              enum_type {
                                name: "BadEnum"
                                value { name: "v1" number: 1 }
                              }
                            )pb")
                            .status();
  EXPECT_EQ(std::string_view(status.message()),
            "for open enums, the first value must be zero (BadEnum)");
}

TEST(ReflectionTest, EnumDefault) {
  upb::DefPool pool = LoadDescriptorProto(
                          R"pb(
                            syntax: "proto2"
                            name: "F"
                            enum_type {
                              name: "FooEnum"
                              value { name: "v1" number: 1 }
                            }
                          )pb")
                          .value();
  upb::EnumDefPtr e = pool.FindEnumByName("FooEnum");
  EXPECT_EQ(e.default_value(), 1);
}

TEST(ReflectionTest, ImplicitPresenceWithDefault) {
  absl::Status status =
      LoadDescriptorProto(
          R"pb(
            syntax: "editions"
            edition: EDITION_2023
            name: "F"
            message_type {
              name: "FooMessage"
              field {
                name: "f1"
                number: 1
                type: TYPE_INT32
                default_value: "1"
                options { features { field_presence: IMPLICIT } }
              }
            }
          )pb")
          .status();
  EXPECT_EQ(std::string_view(status.message()),
            "fields with implicit presence cannot have explicit defaults "
            "(FooMessage.f1)");
}

TEST(ReflectionTest, ImplicitPresenceWithNonZeroDefaultEnum) {
  absl::Status status =
      LoadDescriptorProto(
          R"pb(
            syntax: "editions"
            edition: EDITION_2023
            name: "F"
            enum_type {
              name: "FooEnum"
              value { name: "v1" number: 1 }
              options { features { enum_type: CLOSED } }
            }
            message_type {
              name: "FooMessage"
              field {
                name: "f1"
                number: 1
                type: TYPE_ENUM
                type_name: "FooEnum"
                options { features { field_presence: IMPLICIT } }
              }
            }
          )pb")
          .status();
  EXPECT_EQ(std::string_view(status.message()),
            "Implicit presence field (FooMessage.f1) cannot use an enum type "
            "with a non-zero default (FooEnum)");
}

TEST(ReflectionTest, EditionWithoutSyntax) {
  absl::Status status = LoadDescriptorProto(
                            R"pb(
                              edition: EDITION_2023
                            )pb")
                            .status();
  EXPECT_EQ(
      status.message(),
      R"(Setting edition requires that syntax="editions", but syntax is "")");
}

TEST(ReflectionTest, EditionWithWrongSyntax) {
  absl::Status status = LoadDescriptorProto(
                            R"pb(
                              edition: EDITION_2023 syntax: "proto2"
                            )pb")
                            .status();
  EXPECT_EQ(
      status.message(),
      R"(Setting edition requires that syntax="editions", but syntax is "proto2")");
}

TEST(ReflectionTest, SyntaxEditionsWithNoEdition) {
  absl::Status status = LoadDescriptorProto(
                            R"pb(
                              syntax: "editions"
                            )pb")
                            .status();
  EXPECT_EQ(status.message(),
            R"(File has syntax="editions", but no edition is specified)");
}

TEST(ReflectionTest, InvalidSyntax) {
  absl::Status status = LoadDescriptorProto(
                            R"pb(
                              syntax: "abc123"
                            )pb")
                            .status();
  EXPECT_EQ(status.message(), R"(Invalid syntax 'abc123')");
}

TEST(ReflectionTest, ExplicitFeatureOnProto2File) {
  absl::Status status = LoadDescriptorProto(
                            R"pb(
                              syntax: "proto2"
                              options { features { field_presence: EXPLICIT } }
                            )pb")
                            .status();
  EXPECT_EQ(status.message(), R"(Features can only be specified for editions)");
}

TEST(ReflectionTest, ExplicitFeatureOnProto2Message) {
  absl::Status status =
      LoadDescriptorProto(
          R"pb(
            syntax: "proto2"
            message_type {
              name: "M"
              options { features { field_presence: EXPLICIT } }
            }
          )pb")
          .status();
  EXPECT_EQ(status.message(), R"(Features can only be specified for editions)");
}

TEST(ReflectionTest, ExplicitFeatureOnProto2Enum) {
  absl::Status status =
      LoadDescriptorProto(
          R"pb(
            syntax: "proto2"
            enum_type {
              name: "E"
              options { features { field_presence: EXPLICIT } }
            }
          )pb")
          .status();
  EXPECT_EQ(status.message(), R"(Features can only be specified for editions)");
}

TEST(ReflectionTest, ExplicitFeatureOnProto2EnumValue) {
  absl::Status status =
      LoadDescriptorProto(
          R"pb(
            syntax: "proto2"
            enum_type {
              name: "E"
              value {
                name: "V"
                options { features { field_presence: EXPLICIT } }
              }
            }
          )pb")
          .status();
  EXPECT_EQ(status.message(), R"(Features can only be specified for editions)");
}

TEST(ReflectionTest, TooManyRequiredFieldsFailGracefully) {
  const auto make_desc = [](int n) {
    std::vector<std::string> fields;
    for (int i = 1; i <= n; ++i) {
      fields.push_back(absl::StrFormat(R"pb(
                                         field {
                                           name: "f%d"
                                           number: %d
                                           type: TYPE_INT32
                                           label: LABEL_REQUIRED
                                         })pb",
                                       i, i));
    }
    return absl::StrFormat(
        R"pb(
          syntax: "proto2"
          name: "F"
          message_type { name: "FooMessage" %s }
        )pb",
        absl::StrJoin(fields, "\n"));
  };
  // 63 required fields is ok.
  upb::DefPool good = LoadDescriptorProto(make_desc(63)).value();
  auto m = good.FindMessageByName("FooMessage");
  auto f = m.FindFieldByNumber(63);
  EXPECT_STREQ(f.full_name(), "FooMessage.f63");

  // 64 is too much.
  EXPECT_THAT(LoadDescriptorProto(make_desc(64)).status().message(),
              HasSubstr("Too many required fields"));
}

#define STRING_AND_SIZE(string) string, strlen(string)

TEST(ReflectionTest, FindMethodByName) {
  upb::Arena arena;
  upb::DefPool defpool;
  upb::Status status;
  ASSERT_TRUE(_upb_DefPool_LoadDefInit(
      defpool.ptr(), &google_protobuf_unittest_proto_upbdefinit));
  const upb_ServiceDef* service_def = upb_DefPool_FindServiceByName(
      defpool.ptr(), "proto2_unittest.TestService");
  ASSERT_THAT(service_def, NotNull());
  EXPECT_STREQ(upb_ServiceDef_Name(service_def), "TestService");
  EXPECT_STREQ(upb_ServiceDef_FullName(service_def),
               "proto2_unittest.TestService");
  EXPECT_EQ(upb_DefPool_FindServiceByNameWithSize(
                defpool.ptr(), STRING_AND_SIZE("proto2_unittest.TestService")),
            service_def);
  const upb_MethodDef* method_def =
      upb_ServiceDef_FindMethodByName(service_def, "Bar");
  ASSERT_THAT(method_def, NotNull());
  EXPECT_STREQ(upb_MethodDef_Name(method_def), "Bar");
  EXPECT_STREQ(upb_MethodDef_FullName(method_def),
               "proto2_unittest.TestService.Bar");
  EXPECT_EQ(upb_ServiceDef_FindMethodByNameWithSize(service_def,
                                                    STRING_AND_SIZE("Bar")),
            method_def);
}

TEST(ReflectionTest, FindEnumByName) {
  upb::Arena arena;
  upb::DefPool defpool;
  upb::Status status;
  ASSERT_TRUE(_upb_DefPool_LoadDefInit(
      defpool.ptr(), &google_protobuf_unittest_proto_upbdefinit));
  const upb_EnumDef* enum_def = upb_DefPool_FindEnumByName(
      defpool.ptr(), "proto2_unittest.TestAllTypes.NestedEnum");
  ASSERT_THAT(enum_def, NotNull());
  EXPECT_STREQ(upb_EnumDef_Name(enum_def), "NestedEnum");
  EXPECT_STREQ(upb_EnumDef_FullName(enum_def),
               "proto2_unittest.TestAllTypes.NestedEnum");
  EXPECT_EQ(upb_DefPool_FindEnumByNameWithSize(
                defpool.ptr(),
                STRING_AND_SIZE("proto2_unittest.TestAllTypes.NestedEnum")),
            enum_def);
}

TEST(ReflectionTest, FindEnumValueByName) {
  upb::Arena arena;
  upb::DefPool defpool;
  upb::Status status;
  ASSERT_TRUE(_upb_DefPool_LoadDefInit(
      defpool.ptr(), &google_protobuf_unittest_proto_upbdefinit));
  const upb_EnumValueDef* enum_value_def = upb_DefPool_FindEnumValueByName(
      defpool.ptr(), "proto2_unittest.TestAllTypes.BAR");
  ASSERT_THAT(enum_value_def, NotNull());
  EXPECT_STREQ(upb_EnumValueDef_Name(enum_value_def), "BAR");
  EXPECT_STREQ(upb_EnumValueDef_FullName(enum_value_def),
               "proto2_unittest.TestAllTypes.BAR");
  EXPECT_EQ(
      upb_DefPool_FindEnumValueByNameWithSize(
          defpool.ptr(), STRING_AND_SIZE("proto2_unittest.TestAllTypes.BAR")),
      enum_value_def);
  const upb_EnumDef* enum_def = upb_DefPool_FindEnumByName(
      defpool.ptr(), "proto2_unittest.TestAllTypes.NestedEnum");
  ASSERT_THAT(enum_def, NotNull());
  EXPECT_EQ(upb_EnumDef_FindValueByName(enum_def, "BAR"), enum_value_def);
  EXPECT_EQ(
      upb_EnumDef_FindValueByNameWithSize(enum_def, STRING_AND_SIZE("BAR")),
      enum_value_def);
}

TEST(ReflectionTest, NegativePublicDependencyIndex) {
  // Verify that a negative public_dependency index is rejected rather than
  // causing an out-of-bounds read when the index is later used to access the
  // deps array.
  absl::Status status =
      LoadDescriptorProto(
          R"pb(
            name: "test.proto"
            public_dependency: -1
          )pb")
          .status();
  EXPECT_FALSE(status.ok());
  EXPECT_THAT(std::string(status.message()), HasSubstr("out of range"));
}

TEST(ReflectionTest, NegativeWeakDependencyIndex) {
  absl::Status status =
      LoadDescriptorProto(
          R"pb(
            name: "test.proto"
            weak_dependency: -1
          )pb")
          .status();
  EXPECT_FALSE(status.ok());
  EXPECT_THAT(std::string(status.message()), HasSubstr("out of range"));
}

TEST(ReflectionTest, ZeroPublicDependencyIndexWithNoDeps) {
  // Index 0 is out of range when there are no dependencies (dep_count=0).
  absl::Status status =
      LoadDescriptorProto(
          R"pb(
            name: "test.proto"
            public_dependency: 0
          )pb")
          .status();
  EXPECT_FALSE(status.ok());
  EXPECT_THAT(std::string(status.message()), HasSubstr("out of range"));
}

TEST(ReflectionTest, ZeroWeakDependencyIndexWithNoDeps) {
  absl::Status status =
      LoadDescriptorProto(
          R"pb(
            name: "test.proto"
            weak_dependency: 0
          )pb")
          .status();
  EXPECT_FALSE(status.ok());
  EXPECT_THAT(std::string(status.message()), HasSubstr("out of range"));
}

// Sets (pb.enumvalue.json).string option (extension field 998) on
// EnumValueOptions.
static void SetCustomJsonOption(google::protobuf::EnumValueOptions* options,
                                absl::string_view json_name) {
  // Wire format for JsonEnumValueOptions { string string = 1 }:
  // Field 1 (string, length-delimited): tag = (1 << 3) | 2 = 0x0a.
  std::string payload;
  payload.push_back('\x0a');
  payload.push_back(static_cast<char>(json_name.size()));
  payload.append(json_name);
  // Field 998 is extension (pb.enumvalue.json) on EnumValueOptions.
  options->mutable_unknown_fields()->AddLengthDelimited(998, payload);
}

struct TestEnumValueSpec {
  std::string name;
  int number;
  std::string json_name;
};

static absl::StatusOr<upb::DefPool> LoadEnumDescriptorWithValues(
    absl::Span<const TestEnumValueSpec> values) {
  google::protobuf::FileDescriptorProto file_proto;
  file_proto.set_name("test.proto");
  file_proto.set_syntax("editions");
  file_proto.set_edition(google::protobuf::EDITION_2026);

  google::protobuf::EnumDescriptorProto* enum_proto = file_proto.add_enum_type();
  enum_proto->set_name("TestEnum");

  for (const TestEnumValueSpec& v : values) {
    google::protobuf::EnumValueDescriptorProto* val_proto = enum_proto->add_value();
    val_proto->set_name(v.name);
    val_proto->set_number(v.number);
    if (!v.json_name.empty()) {
      SetCustomJsonOption(val_proto->mutable_options(), v.json_name);
    }
  }

  google::protobuf::FileDescriptorSet set;
  *set.add_file() = file_proto;
  return LoadDescriptorSetFromProto(set);
}

TEST(ReflectionTest, EnumCustomJsonNameConflictDifferentNumberFails) {
  // Two enum values with DIFFERENT numbers (1 and 2) sharing the same custom
  // JSON name must fail descriptor validation.
  absl::Status status =
      LoadEnumDescriptorWithValues({
                                       {"VAL_ZERO", 0, ""},
                                       {"VAL_A", 1, "custom_name"},
                                       {"VAL_B", 2, "custom_name"},
                                   })
          .status();
  EXPECT_FALSE(status.ok());
  EXPECT_THAT(std::string(status.message()),
              HasSubstr("duplicate custom json_name (custom_name) in enum"));
}

TEST(ReflectionTest, EnumCustomJsonNameAliasedSameNumberSucceeds) {
  // Two aliased enum values with the SAME number (1 and 1) sharing the same
  // custom JSON name must succeed descriptor validation.
  absl::Status status =
      LoadEnumDescriptorWithValues({
                                       {"VAL_ZERO", 0, ""},
                                       {"VAL_A", 1, "custom_name"},
                                       {"VAL_B", 1, "custom_name"},
                                   })
          .status();
  EXPECT_TRUE(status.ok()) << status.message();
}

TEST(ReflectionTest, DefPoolFindMethodsRespectSize) {
  upb::DefPool pool = LoadDescriptorProto(R"pb(
                        syntax: "proto2"
                        name: "test.proto"
                        package: "pkg"
                        message_type {
                          name: "TestMessage"
                          extension_range { start: 1 end: 1000 }
                        }
                        enum_type {
                          name: "TestEnum"
                          value { name: "VAL" number: 1 }
                        }
                        extension {
                          name: "test_ext"
                          number: 100
                          label: LABEL_OPTIONAL
                          type: TYPE_INT32
                          extendee: ".pkg.TestMessage"
                        }
                      )pb")
                          .value();

  // Test FindMessageByName
  {
    absl::string_view full_name = "pkg.TestMessage";
    EXPECT_TRUE(pool.FindMessageByName(full_name));
    EXPECT_FALSE(
        pool.FindMessageByName(full_name.substr(0, 12)));  // "pkg.TestMess"
  }

  // Test FindEnumByName
  {
    absl::string_view full_name = "pkg.TestEnum";
    EXPECT_TRUE(pool.FindEnumByName(full_name));
    EXPECT_FALSE(pool.FindEnumByName(full_name.substr(0, 10)));  // "pkg.TestEn"
  }

  // Test FindFileByName
  {
    absl::string_view full_name = "test.proto";
    EXPECT_TRUE(pool.FindFileByName(full_name));
    EXPECT_FALSE(pool.FindFileByName(full_name.substr(0, 7)));  // "test.pr"
  }

  // Test FindExtensionByName
  {
    absl::string_view full_name = "pkg.test_ext";
    EXPECT_TRUE(pool.FindExtensionByName(full_name));
    EXPECT_FALSE(
        pool.FindExtensionByName(full_name.substr(0, 11)));  // "pkg.test_ex"
  }
}

}  // namespace
}  // namespace upb_test
