// Protocol Buffers - Google's data interchange format
// Copyright 2023 Google Inc.  All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd

#include <memory>

#include "google/protobuf/descriptor.pb.h"
#include <gtest/gtest.h>
#include "absl/strings/string_view.h"
#include "google/protobuf/compiler/command_line_interface_tester.h"
#include "google/protobuf/compiler/php/php_generator.h"

namespace google {
namespace protobuf {
namespace compiler {
namespace php {
namespace {

class PhpGeneratorTest : public CommandLineInterfaceTester {
 protected:
  PhpGeneratorTest() {
    RegisterGenerator("--php_out", "--php_opt", std::make_unique<Generator>(),
                      "PHP test generator");

    // Generate built-in protos.
    CreateTempFile(
        "google/protobuf/descriptor.proto",
        google::protobuf::DescriptorProto::descriptor()->file()->DebugString());
  }
};

TEST_F(PhpGeneratorTest, Basic) {
  CreateTempFile("foo.proto",
                 R"schema(
    syntax = "proto3";
    message Foo {
      optional int32 bar = 1;
      int32 baz = 2;
    })schema");

  RunProtoc(
      "protocol_compiler --proto_path=$tmpdir --php_out=$tmpdir foo.proto");

  ExpectNoErrors();
}

TEST_F(PhpGeneratorTest, Proto2File) {
  CreateTempFile("foo.proto",
                 R"schema(
    syntax = "proto2";
    message Foo {
      optional int32 bar = 1;
    })schema");

  RunProtoc(
      "protocol_compiler --proto_path=$tmpdir --php_out=$tmpdir foo.proto");

  ExpectNoErrors();
}

TEST_F(PhpGeneratorTest, RequiredFieldError) {
  CreateTempFile("foo.proto",
                 R"schema(
    syntax = "proto2";
    message FooBar {
      required int32 foo_message = 1;
    })schema");

  RunProtoc(
      "protocol_compiler --proto_path=$tmpdir --php_out=$tmpdir foo.proto");

  ExpectErrorSubstring(
      "Can't generate PHP code for required field FooBar.foo_message");
}

TEST_F(PhpGeneratorTest, GroupFieldError) {
  CreateTempFile("foo.proto",
                 R"schema(
    syntax = "proto2";
    message Foo {
      optional group Bar = 1 {
        optional int32 baz = 1;
      };
    })schema");

  RunProtoc(
      "protocol_compiler --proto_path=$tmpdir --php_out=$tmpdir foo.proto");

  ExpectErrorSubstring("Can't generate PHP code for group field Foo.bar");
}

TEST_F(PhpGeneratorTest, ClosedEnumError) {
  CreateTempFile("foo.proto",
                 R"schema(
    syntax = "proto2";
    enum Foo {
      BAR = 0;
    })schema");

  RunProtoc(
      "protocol_compiler --proto_path=$tmpdir --php_out=$tmpdir foo.proto");

  ExpectErrorSubstring("Can't generate PHP code for closed enum Foo");
}

TEST_F(PhpGeneratorTest, ImportPublic) {
  CreateTempFile("common.proto",
                 R"schema(
    syntax = "proto3";
    package prototest;
    message Common {
      string data = 1;
    })schema");

  CreateTempFile("prototest.proto",
                 R"schema(
    syntax = "proto3";
    package prototest;
    import public "common.proto";
    )schema");

  CreateTempFile("usecase.proto",
                 R"schema(
    syntax = "proto3";
    package usecasetest;
    import "prototest.proto";
    message UseCase {
      prototest.Common commonUse = 1;
    })schema");

  RunProtoc(
      "protocol_compiler --proto_path=$tmpdir --php_out=$tmpdir "
      "common.proto prototest.proto usecase.proto");

  ExpectNoErrors();
}

TEST_F(PhpGeneratorTest, InvalidPhpNamespaceRejected) {
  CreateTempFile("foo.proto",
                 R"schema(
    syntax = "proto3";
    option php_namespace = "App\\Models;evil();//";
    message Foo {
      int32 bar = 1;
    })schema");

  RunProtoc(
      "protocol_compiler --proto_path=$tmpdir --php_out=$tmpdir foo.proto");

  ExpectErrorSubstring("Invalid character");
}

TEST_F(PhpGeneratorTest, InvalidPhpMetadataNamespaceRejected) {
  CreateTempFile("foo.proto",
                 R"schema(
    syntax = "proto3";
    option php_metadata_namespace = "GPBMetadata{system('rm -rf /')}";
    message Foo {
      int32 bar = 1;
    })schema");

  RunProtoc(
      "protocol_compiler --proto_path=$tmpdir --php_out=$tmpdir foo.proto");

  ExpectErrorSubstring("Invalid character");
}

TEST_F(PhpGeneratorTest, ValidPhpNamespaceAccepted) {
  CreateTempFile("foo.proto",
                 R"schema(
    syntax = "proto3";
    option php_namespace = "App\\Models\\V2";
    message Foo {
      int32 bar = 1;
    })schema");

  RunProtoc(
      "protocol_compiler --proto_path=$tmpdir --php_out=$tmpdir foo.proto");

  ExpectNoErrors();
}

TEST_F(PhpGeneratorTest, UnicodePhpNamespaceAccepted) {
  CreateTempFile("foo.proto",
                 R"schema(
    syntax = "proto3";
    option php_namespace = "App\\Models\\V2";
    message Foo {
      int32 bar = 1;
    })schema");

  RunProtoc(
      "protocol_compiler --proto_path=$tmpdir --php_out=$tmpdir foo.proto");

  ExpectNoErrors();
}

TEST_F(PhpGeneratorTest, InvalidPhpClassPrefixRejected) {
  // The prefix is pasted straight into `class <prefix><Name> extends ...`, so
  // without validation this generates a second, attacker-defined class while
  // leaving the real one intact.
  CreateTempFile("foo.proto",
                 R"schema(
    syntax = "proto3";
    option php_class_prefix = "Pfx{} class Injected {} class ";
    message Foo {
      int32 bar = 1;
    })schema");

  RunProtoc(
      "protocol_compiler --proto_path=$tmpdir --php_out=$tmpdir foo.proto");

  ExpectErrorSubstring("Invalid character");
}

TEST_F(PhpGeneratorTest, ValidPhpClassPrefixAccepted) {
  CreateTempFile("foo.proto",
                 R"schema(
    syntax = "proto3";
    option php_class_prefix = "Pfx";
    message Foo {
      int32 bar = 1;
    })schema");

  RunProtoc(
      "protocol_compiler --proto_path=$tmpdir --php_out=$tmpdir foo.proto");

  ExpectNoErrors();
}

TEST_F(PhpGeneratorTest, CustomEnumNames) {
  CreateTempFile("google/protobuf/json_enumvalue_options.proto", R"schema(
    edition = "2024";
    package pb.enumvalue;
    import "google/protobuf/descriptor.proto";
    message JsonEnumValueOptions {
      string string = 1;
    }
    extend google.protobuf.EnumValueOptions {
      JsonEnumValueOptions json = 998;
    }
  )schema");

  CreateTempFile("foo.proto", R"schema(
    edition = "2026";
    package foo;
    import "google/protobuf/json_enumvalue_options.proto";
    enum MyEnum {
      MY_ENUM_UNKNOWN = 0;
      MY_ENUM_BAR = 1 [(pb.enumvalue.json).string = "custom_bar"];
      MY_ENUM_BAZ = 2;
    }
  )schema");

  RunProtoc(
      "protocol_compiler --proto_path=$tmpdir --php_out=$tmpdir foo.proto");

  ExpectNoErrors();
  ExpectFileContentContainsSubstring(
      "GPBMetadata/Foo.php", "\"foo.MyEnum.MY_ENUM_BAR\" => \"custom_bar\"");
}

}  // namespace
}  // namespace php
}  // namespace compiler
}  // namespace protobuf
}  // namespace google
