//
//
// Copyright 2023 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.
//
//

#ifndef GRPC_TEST_CPP_EXT_FILTERS_LOGGING_LIBRARY_H
#define GRPC_TEST_CPP_EXT_FILTERS_LOGGING_LIBRARY_H

#include <chrono>
#include <thread>  // NOLINT

#include "absl/strings/escaping.h"
#include "absl/strings/str_cat.h"
#include "gmock/gmock.h"
#include "google/protobuf/text_format.h"
#include "gtest/gtest.h"

#include <grpc++/grpc++.h>
#include <grpcpp/support/status.h>

#include "src/core/ext/filters/logging/logging_filter.h"
#include "src/core/lib/gprpp/sync.h"
#include "src/cpp/ext/gcp/observability_logging_sink.h"
#include "src/proto/grpc/testing/echo.grpc.pb.h"
#include "src/proto/grpc/testing/echo_messages.pb.h"
#include "test/core/util/port.h"
#include "test/core/util/test_config.h"
#include "test/cpp/end2end/test_service_impl.h"

namespace grpc {
namespace testing {

class MyTestServiceImpl : public TestServiceImpl {
 public:
  Status Echo(ServerContext* context, const EchoRequest* request,
              EchoResponse* response) override {
    context->AddInitialMetadata("server-header-key", "server-header-value");
    context->AddTrailingMetadata("server-trailer-key", "server-trailer-value");
    return TestServiceImpl::Echo(context, request, response);
  }
};

class TestLoggingSink : public grpc_core::LoggingSink {
 public:
  Config FindMatch(bool /* is_client */, absl::string_view /* service */,
                   absl::string_view /* method */) override {
    grpc_core::MutexLock lock(&mu_);
    return config_;
  }

  void LogEntry(Entry entry) override {
    ::google::protobuf::Struct json;
    grpc::internal::EntryToJsonStructProto(entry, &json);
    std::string output;
    ::google::protobuf::TextFormat::PrintToString(json, &output);
    gpr_log(GPR_INFO, "%s", output.c_str());
    gpr_log(GPR_INFO, "trace_id: %s", entry.trace_id.c_str());
    gpr_log(GPR_INFO, "span_id: %s", entry.span_id.c_str());
    gpr_log(GPR_INFO, "is_sampled: %d", entry.is_sampled);
    grpc_core::MutexLock lock(&mu_);
    entries_.push_back(std::move(entry));
  }

  void SetConfig(Config config) {
    grpc_core::MutexLock lock(&mu_);
    config_ = config;
  }

  std::vector<LoggingSink::Entry> entries() {
    grpc_core::MutexLock lock(&mu_);
    return entries_;
  }

  void Clear() {
    grpc_core::MutexLock lock(&mu_);
    entries_.clear();
  }

 private:
  grpc_core::Mutex mu_;
  std::vector<LoggingSink::Entry> entries_ ABSL_GUARDED_BY(mu_);
  Config config_ ABSL_GUARDED_BY(mu_);
};

extern TestLoggingSink* g_test_logging_sink;

class LoggingTest : public ::testing::Test {
 protected:
  static void SetUpTestSuite() {
    g_test_logging_sink = new TestLoggingSink;
    grpc_core::RegisterLoggingFilter(g_test_logging_sink);
  }

  void SetUp() override {
    // Clean up previous entries
    g_test_logging_sink->Clear();
    // Set up a synchronous server on a different thread to avoid the asynch
    // interface.
    grpc::ServerBuilder builder;
    int port = grpc_pick_unused_port_or_die();
    server_address_ = absl::StrCat("localhost:", port);
    // Use IPv4 here because it's less flaky than IPv6 ("[::]:0") on Travis.
    builder.AddListeningPort(server_address_, grpc::InsecureServerCredentials(),
                             &port);
    builder.RegisterService(&service_);
    server_ = builder.BuildAndStart();
    ASSERT_NE(nullptr, server_);

    server_thread_ = std::thread(&LoggingTest::RunServerLoop, this);

    stub_ = EchoTestService::NewStub(grpc::CreateChannel(
        server_address_, grpc::InsecureChannelCredentials()));
  }

  void ResetStub(std::shared_ptr<Channel> channel) {
    stub_ = EchoTestService::NewStub(std::move(channel));
  }

  void TearDown() override {
    server_->Shutdown();
    server_thread_.join();
  }

  void RunServerLoop() { server_->Wait(); }

  std::string server_address_;
  MyTestServiceImpl service_;
  std::unique_ptr<grpc::Server> server_;
  std::thread server_thread_;

  std::unique_ptr<EchoTestService::Stub> stub_;
};

}  // namespace testing
}  // namespace grpc

#endif  // GRPC_TEST_CPP_EXT_FILTERS_LOGGING_LIBRARY_H
