// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

/**
 * @file
 * @brief The base class for the common behavior of the transport adapter tests.
 *
 * @brief Any HTTP transport adapter can be used for this tests.
 *
 */

#include <azure/core/http/http.hpp>
#include <azure/core/internal/diagnostics/log.hpp>
#include <azure/core/internal/environment.hpp>
#include <azure/core/internal/http/pipeline.hpp>
#include <azure/core/io/body_stream.hpp>
#include <azure/core/platform.hpp>
#include <azure/core/url.hpp>

#include <exception>
#include <memory>
#include <string>
#include <vector>

#include <gtest/gtest.h>

#if defined(AZ_PLATFORM_MAC)
#define _azure_DISABLE_HTTP_BIN_TESTS 1
#endif

namespace Azure { namespace Core { namespace Test {

  namespace _detail {
    static Core::Url GetAzureSdkHttpbinServerUrl()
    {
      using Core::Diagnostics::Logger;
      using Core::Diagnostics::_internal::Log;

      constexpr const char HttpBinUrlEnvVarName[] = "AZSDKCPPTEST_HTTPBIN_URL";
      auto const httpBinEnvVar = Core::_internal::Environment::GetVariable(HttpBinUrlEnvVarName);
      if (httpBinEnvVar.empty())
      {
        Log::Stream(Logger::Level::Verbose) << '\'' << HttpBinUrlEnvVarName << "' not set.";
        return {};
      }

      try
      {
        Core::Url const httpBinUrl(httpBinEnvVar);
        Log::Stream(Logger::Level::Verbose)
            << HttpBinUrlEnvVarName << "= { Scheme: \"" << httpBinUrl.GetScheme() << "\", Host: \""
            << httpBinUrl.GetHost() << "\"}.";

        return httpBinUrl;
      }
      catch (std::exception const&)
      {
        Log::Stream(Logger::Level::Warning)
            << "Error parsing '" << HttpBinUrlEnvVarName << "' (=\"" << httpBinEnvVar << "\").";
        return {};
      }
    }

    static std::string const AzureSdkHttpbinServerSchema
        = GetAzureSdkHttpbinServerUrl().GetScheme();

    static std::string const AzureSdkHttpbinServer = GetAzureSdkHttpbinServerUrl().GetHost();
  } // namespace _detail

  struct AzureSdkHttpbinServer final
  {
    inline static bool IsEnabled() { return !Schema().empty() && !Host().empty(); }
    inline static std::string Get() { return Schema() + "://" + Host() + "/get"; }
    inline static std::string Headers() { return Schema() + "://" + Host() + "/headers"; }
    inline static std::string GetWithPort() { return Schema() + "://" + Host() + ":443/get"; }
    inline static std::string Put() { return Schema() + "://" + Host() + "/put"; }
    inline static std::string Delete() { return Schema() + "://" + Host() + "/delete"; }
    inline static std::string Patch() { return Schema() + "://" + Host() + "/patch"; }
    inline static std::string Delay() { return Schema() + "://" + Host() + "/delay"; }
    inline static std::string Status(int statusCode)
    {
      return Schema() + "://" + Host() + "/status/" + std::to_string(statusCode);
    }
    inline static std::string ResponseHeaders(std::string responseHeaders)
    {
      return Schema() + "://" + Host() + "/response-headers?" + responseHeaders;
    }
    inline static std::string RedirectTo(std::string redirectToUrl)
    {
      return Schema() + "://" + Host() + "/redirect-to?url=" + redirectToUrl;
    }
    inline static std::string Host() { return std::string(_detail::AzureSdkHttpbinServer); }
    inline static std::string Schema() { return std::string(_detail::AzureSdkHttpbinServerSchema); }
  };

  struct TransportAdaptersTestParameter final
  {
    std::string Suffix;
    Azure::Core::Http::Policies::TransportOptions TransportAdapter;

    TransportAdaptersTestParameter(
        std::string suffix,
        Azure::Core::Http::Policies::TransportOptions options)
        : Suffix(std::move(suffix)), TransportAdapter(std::move(options))
    {
    }
  };

  class TransportAdapter : public testing::TestWithParam<TransportAdaptersTestParameter> {
  protected:
    std::unique_ptr<Azure::Core::Http::_internal::HttpPipeline> m_pipeline;

    // Befor each test, create pipeline
    virtual void SetUp() override
    {
      std::vector<std::unique_ptr<Azure::Core::Http::Policies::HttpPolicy>> retryPolicies;
      std::vector<std::unique_ptr<Azure::Core::Http::Policies::HttpPolicy>> policies;

      Azure::Core::_internal::ClientOptions op;
      op.Retry.RetryDelay = std::chrono::milliseconds(10);
      op.Transport = GetParam().TransportAdapter;
      m_pipeline = std::make_unique<Azure::Core::Http::_internal::HttpPipeline>(
          op, "TransportTest", "X.X", std::move(retryPolicies), std::move(policies));
    }

    static void CheckBodyFromBuffer(
        Azure::Core::Http::RawResponse& response,
        int64_t size,
        std::string expectedBody = std::string(""));

    static void CheckBodyFromStream(
        Azure::Core::Http::RawResponse& response,
        int64_t size,
        std::string expectedBody = std::string(""));

    static void checkResponseCode(
        Azure::Core::Http::HttpStatusCode code,
        Azure::Core::Http::HttpStatusCode expectedCode = Azure::Core::Http::HttpStatusCode::Ok);
  };

}}} // namespace Azure::Core::Test
