/* Copyright (c) 2020, 2025, Oracle and/or its affiliates.

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License, version 2.0,
 as published by the Free Software Foundation.

 This program is designed to work with certain software (including
 but not limited to OpenSSL) that is licensed under separate terms,
 as designated in a particular file or component or in included license
 documentation.  The authors of MySQL hereby grant you an additional
 permission to link the program and your derivative works with the
 separately licensed software that they have either included with
 the program or referenced in the documentation.

 This program is distributed in the hope that it will be useful,  but
 WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
 the GNU General Public License, version 2.0, for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software Foundation, Inc.,
 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */

#ifndef UNITTEST_MYSQLSHDK_LIBS_OCI_OCI_TESTS_H_
#define UNITTEST_MYSQLSHDK_LIBS_OCI_OCI_TESTS_H_

#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <memory>
#include <string>
#include <vector>

#include "unittest/gtest_clean.h"
#include "unittest/test_utils.h"

#include "mysqlshdk/include/scripting/lang_base.h"
#include "mysqlshdk/include/scripting/types.h"
#include "mysqlshdk/include/shellcore/shell_options.h"
#include "mysqlshdk/libs/oci/oci_bucket.h"
#include "mysqlshdk/libs/oci/oci_bucket_options.h"
#include "mysqlshdk/libs/utils/utils_file.h"
#include "mysqlshdk/libs/utils/utils_string.h"

extern "C" const char *g_oci_config_path;

namespace mysqlshdk {
namespace oci {

#define SKIP_IF_NO_OCI_CONFIGURATION \
  do {                               \
    if (should_skip()) {             \
      SKIP_TEST(skip_reason());      \
    }                                \
  } while (false)

class Oci_tests : public Shell_core_test_wrapper {
 protected:
  void SetUp() override {
    Shell_core_test_wrapper::SetUp();

    m_oci_config_file = mysqlsh::current_shell_options()->get().oci_config_file;

    if (!shcore::path_exists(m_oci_config_file)) {
      skip("OCI Configuration does not exist: " + m_oci_config_file);
    }

    const auto read_var = [this](const char *name, std::string *out,
                                 bool required = true) {
      const auto var = getenv(name);

      if (var) {
        *out = var;
      } else if (required) {
        skip("Missing environment variable: " + std::string(name));
      }
    };

    read_var("OS_NAMESPACE", &m_os_namespace, false);
    read_var("OCI_COMPARTMENT_ID", &m_oci_compartment_id);
    read_var("OS_BUCKET_NAME", &m_os_bucket_name);
  }

  void skip(const std::string &reason) { m_skip_reasons.emplace_back(reason); }

  bool should_skip() const { return !m_skip_reasons.empty(); }

  std::string skip_reason() const {
    return shcore::str_join(m_skip_reasons, "\n");
  }

  std::string m_oci_config_file;
  std::string m_os_namespace;
  std::string m_os_bucket_name;
  std::string m_oci_compartment_id;

 private:
  std::vector<std::string> m_skip_reasons;
};

class Oci_os_tests : public Oci_tests {
 protected:
  void SetUp() override {
    Oci_tests::SetUp();

    // Note that these object names are in alphabetical order on purpose
    m_objects = {"sakila.sql",
                 "sakila/actor.csv",
                 "sakila/actor_metadata.txt",
                 "sakila/address.csv",
                 "sakila/address_metadata.txt",
                 "sakila/category.csv",
                 "sakila/category_metadata.txt",
                 "sakila_metadata.txt",
                 "sakila_tables.txt",
                 "uncommon%25%name.txt",
                 "uncommon's name.txt"};

    create_bucket();
  }

  void TearDown() override { clean_bucket(); }

  void create_objects(mysqlshdk::oci::Oci_bucket &bucket) {
    for (const auto &name : m_objects) {
      bucket.put_object(name, "0", 1);
    }
  }

  std::shared_ptr<mysqlshdk::oci::Oci_bucket_config> get_config(
      const std::string &bucket = {}) {
    const auto options = shcore::make_dict(
        mysqlshdk::oci::Oci_bucket_options::bucket_name_option(),
        bucket.empty() ? m_os_bucket_name : bucket);

    if (!m_os_namespace.empty()) {
      options->set(mysqlshdk::oci::Oci_bucket_options::namespace_option(),
                   shcore::Value{m_os_namespace});
    }

    mysqlshdk::oci::Oci_bucket_options parsed_options;
    mysqlshdk::oci::Oci_bucket_options::options().unpack(options,
                                                         &parsed_options);

    return std::make_shared<mysqlshdk::oci::Oci_bucket_config>(parsed_options);
  }

  void clean_bucket(mysqlshdk::oci::Oci_bucket &bucket,
                    bool clean_uploads = true, bool clean_pars = true) {
    auto objects = bucket.list_objects();

    if (!objects.empty()) {
      for (const auto &object : objects) {
        bucket.delete_object(object.name);
      }

      objects = bucket.list_objects();
      EXPECT_TRUE(objects.empty());
    }

    if (clean_uploads) {
      auto uploads = bucket.list_multipart_uploads();
      if (!uploads.empty()) {
        for (const auto &upload : uploads) {
          bucket.abort_multipart_upload(upload);
        }

        uploads = bucket.list_multipart_uploads();
        EXPECT_TRUE(uploads.empty());
      }
    }

    if (clean_pars) {
      auto pars = bucket.list_preauthenticated_requests();
      if (!pars.empty()) {
        for (const auto &par : pars) {
          bucket.delete_pre_authenticated_request(par.id);
        }

        pars = bucket.list_preauthenticated_requests();
        EXPECT_TRUE(pars.empty());
      }
    }
  }

  std::vector<std::string> m_objects;

 private:
  void create_bucket() {
    if (!should_skip()) {
      mysqlshdk::oci::Oci_bucket bucket(get_config());

      if (bucket.exists()) {
        clean_bucket(bucket);
      } else {
        bucket.create(m_oci_compartment_id);
      }
    }
  }

  void clean_bucket() {
    if (!should_skip()) {
      mysqlshdk::oci::Oci_bucket bucket(get_config());

      clean_bucket(bucket);
    }
  }
};

}  // namespace oci
}  // namespace mysqlshdk

#endif  // UNITTEST_MYSQLSHDK_LIBS_OCI_OCI_TESTS_H_
