/*
 * Copyright (c) 2024, 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
 */
#include "unittest/gprod_clean.h"

#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <vector>

#include "modules/util/upgrade_checker/common.h"
#include "modules/util/upgrade_checker/feature_life_cycle_check.h"
#include "modules/util/upgrade_checker/sql_upgrade_check.h"
#include "modules/util/upgrade_checker/upgrade_check.h"
#include "modules/util/upgrade_checker/upgrade_check_config.h"
#include "modules/util/upgrade_checker/upgrade_check_creators.h"
#include "modules/util/upgrade_checker/upgrade_check_registry.h"
#include "unittest/modules/util/upgrade_checker/test_utils.h"
#include "unittest/test_utils.h"

namespace mysqlsh {
namespace upgrade_checker {

TEST(Upgrade_check_registry, messages) {
  // checkTableOutput is available all the time as it has no conditions
  Upgrade_check_config config =
      create_config(Version(5, 7, 44), Version(8, 0, 0), "");
  config.set_targets(Target_flags::all());

  auto checklist = Upgrade_check_registry::create_checklist(config, true);

  using Tag_list = std::vector<std::string>;
  using Additional_checks = std::pair<bool, Tag_list>;
  std::unordered_map<std::string_view, Additional_checks> additional_checks;

  // By default, all checks must have title, description, link and no additional
  // tags
  for (const auto &id : ids::all) {
    additional_checks[id] = {true, {}};
  }

  // If not the case, the correct info should be set
  additional_checks[ids::k_innodb_rowformat_check] = {false, {}};
  additional_checks[ids::k_table_command_check] = {false, {}};
  additional_checks[ids::k_schema_inconsistency_check] = {false, {}};
  additional_checks[ids::k_fts_in_tablename_check] = {false, {}};
  additional_checks[ids::k_engine_mixup_check] = {false, {}};
  additional_checks[ids::k_old_geometry_types_check] = {false, {}};
  additional_checks[ids::k_orphaned_objects_check] = {false,
                                                      {"routine", "event"}};
  additional_checks[ids::k_dollar_sign_name_check] = {false, {}};
  additional_checks[ids::k_index_too_large_check] = {false, {}};
  additional_checks[ids::k_empty_dot_table_syntax_check] = {false, {}};
  additional_checks[ids::k_invalid_engine_foreign_key_check] = {false, {}};
  additional_checks[ids::k_deprecated_default_auth_check] = {false, {}};
  additional_checks[ids::k_auth_method_usage_check] = {
      false,
      {"description.Notice", "description.Warning", "description.Error",
       "description.Notice.Replacement", "description.Warning.Replacement",
       "description.Error.Replacement", "docLink.authentication_fido",
       "docLink.sha256_password", "docLink.mysql_native_password"}};

  additional_checks[ids::k_plugin_usage_check] = {
      false,
      {"description.Notice", "description.Warning", "description.Error",
       "description.Notice.Replacement", "description.Warning.Replacement",
       "description.Error.Replacement", "docLink.authentication_fido",
       "docLink.keyring_file", "docLink.keyring_encrypted_file",
       "docLink.keyring_oci"}};

  additional_checks[ids::k_obsolete_sql_mode_flags_check] = {
      true, {"description.Notice", "description.Warning", "description.Error"}};

  additional_checks[ids::k_column_definition] = {
      false, {"floatAutoIncrement", "doubleAutoIncrement"}};
  additional_checks[ids::k_invalid_privileges_check] = {false, {}};
  additional_checks[ids::k_partitions_with_prefix_keys] = {true, {"issue"}};
  additional_checks[ids::k_sys_vars_check] = {
      false,
      {"description.removed", "description.removedLogging",
       "description.allowedValues", "description.forbiddenValues",
       "description.newDefaults", "description.deprecated", "issue.deprecated",
       "issue.removed", "issue.removedLogging", "issue.newDefaults",
       "issue.allowedValues", "issue.forbiddenValues", "replacement",
       "docLink.removed", "docLink.removedLogging"}};
  additional_checks[ids::k_foreign_key_references] = {
      false,
      {"fkToNonUniqueKey", "fkToPartialKey", "solution", "solution1",
       "solution2"}};
  additional_checks[ids::k_syntax_check] = {true, {"warning"}};
  additional_checks[ids::k_spatial_index] = {false, {}};

  using Tag_token_list = std::unordered_map<std::string, Token_definitions>;
  std::unordered_map<std::string_view, Tag_token_list> additional_tokens;

  additional_tokens[ids::k_syntax_check] = {
      {"description", {{"selected_version", "8.0.36"}}},
  };
  additional_tokens[ids::k_spatial_index] = {
      {"description", {{"target_version", MYSH_VERSION}}},
  };

  for (const auto &check : checklist) {
    std::string name = check->get_name();

    std::vector<std::string> tags{"title", "description", "docLink"};

    auto replace_tokens = [&](const std::string &tag,
                              const std::string &value) {
      if (!additional_tokens.contains(name)) {
        return value;
      }
      const auto &tokens = additional_tokens.at(name);
      if (!tokens.contains(tag)) {
        return value;
      }
      return resolve_tokens(value, tokens.at(tag));
    };

    for (const std::string &tag : tags) {
      std::string full_tag = name;
      full_tag.append(".");
      full_tag.append(tag);

      SCOPED_TRACE(
          shcore::str_format("Missing translation for %s", full_tag.c_str()));

      // This guaranteeds the test is properly updated with new checks.
      EXPECT_NO_THROW(additional_checks.at(name));

      const std::string &value = get_translation(full_tag.c_str()).c_str();

      if (tag.compare("docLink") == 0) {
        EXPECT_NE(additional_checks.at(name).first, value.empty());
      } else {
        EXPECT_FALSE(value.empty());
      }
      if (tag.compare("title") == 0) {
        EXPECT_STREQ(check->get_title().c_str(), value.c_str());
      } else if (tag.compare("description") == 0) {
        EXPECT_STREQ(check->get_description().c_str(),
                     replace_tokens(tag, value).c_str());
      }
    }

    for (const auto &tag : additional_checks.at(name).second) {
      const auto full_tag = name + "." + tag;
      SCOPED_TRACE(
          shcore::str_format("Error in translation for %s", full_tag.c_str())
              .c_str());
      const std::string &value = get_translation(full_tag.c_str());
      EXPECT_FALSE(value.empty());
      EXPECT_STREQ(check->get_text(tag.c_str()).c_str(),
                   replace_tokens(tag, value).c_str());
    }
  }
}  // namespace upgrade_checker
namespace {

std::set<std::string_view> positive;
std::set<std::string_view> negative;

bool has_sys_var(Sysvar_check *ch, const std::string &name,
                 const std::string &group) {
  auto it = std::ranges::find_if(
      ch->get_checks(), [&](const auto &item) { return item.name == name; });
  if (it == ch->get_checks().end()) return false;
  if (group == "allowedValues") return !it->allowed_values.empty();
  return false;
}

void test_check_availability(
    std::string_view name, bool availability,
    const std::vector<std::pair<Version, Version>> &versions,
    const std::string &feature = "", const std::string &server_os = "",
    const std::string &group = "") {
  // Track the checks that have been verified
  if (availability) {
    positive.insert(name);
  } else {
    negative.insert(name);
  }

  for (const auto &item : versions) {
    auto ui = mysqlsh::upgrade_checker::create_config(item.first, item.second,
                                                      server_os);
    ui.set_targets(Target_flags::all());

    auto checklist = Upgrade_check_registry::create_checklist(ui);

    bool found = false;
    for (const auto &check : checklist) {
      if (check->get_name() == name) {
        if (feature.empty()) {
          found = true;
        } else {
          const auto feature_check =
              dynamic_cast<Feature_life_cycle_check *>(check.get());
          if (feature_check) {
            found = feature_check->has_feature(feature);
            break;
          }
          const auto sys_var_values_check =
              dynamic_cast<Sysvar_check *>(check.get());
          if (sys_var_values_check) {
            found = has_sys_var(sys_var_values_check, feature, group);
            break;
          }
        }
        break;
      }
    }

    std::string feature_description{feature};
    if (!feature.empty()) {
      feature_description.append(" in ");
    }

    SCOPED_TRACE(shcore::str_format(
        "Validating %s of %supgrade check named "
        "'%.*s' using start version as "
        "%s and target version as %s",
        availability ? "existence" : "inexistence", feature_description.c_str(),
        static_cast<int>(name.length()), name.data(),
        item.first.get_base().c_str(), item.second.get_base().c_str()));
    EXPECT_EQ(found, availability);
  }
}

}  // namespace

TEST(Upgrade_check_registry, create_checklist) {
  std::map<Version, std::set<std::string_view>> single_version_checks = {
      {Version(8, 0, 0),
       {ids::k_invalid_57_names_check, ids::k_orphaned_objects_check,
        ids::k_index_too_large_check, ids::k_empty_dot_table_syntax_check,
        ids::k_invalid_engine_foreign_key_check}},
      {Version(8, 0, 11),
       {ids::k_old_temporal_check, ids::k_utf8mb3_check,
        ids::k_mysql_schema_check, ids::k_nonnative_partitioning_check,
        ids::k_foreign_key_length_check, ids::k_maxdb_sql_mode_flags_check,
        ids::k_obsolete_sql_mode_flags_check,
        ids::k_enum_set_element_length_check, ids::k_removed_functions_check,
        ids::k_zero_dates_check, ids::k_schema_inconsistency_check,
        ids::k_engine_mixup_check,
        ids::k_old_geometry_types_check,  // REVIEW OTHER NEGATIVE TEST
        ids::k_default_authentication_plugin_check,
        ids::k_default_authentication_plugin_mds_check}},
      {Version(8, 0, 12), {ids::k_columns_which_cannot_have_defaults_check}},
      {Version(8, 0, 13), {ids::k_groupby_asc_syntax_check}},
      {Version(8, 0, 17), {ids::k_circular_directory_check}},
      {Version(8, 0, 29), {ids::k_deprecated_temporal_delimiter_check}},
      {Version(8, 0, 31), {ids::k_dollar_sign_name_check}},
      {Version(8, 4, 0),
       {ids::k_column_definition, ids::k_partitions_with_prefix_keys}}};

  auto v5_7_0 = Version(5, 7, 0);
  for (const auto &version_check : single_version_checks) {
    for (const auto check : version_check.second) {
      test_check_availability(
          check, true,
          {{v5_7_0, version_check.first},
           {before_version(version_check.first), version_check.first},
           {v5_7_0, after_version(version_check.first)}});

      test_check_availability(
          check, false,
          {{v5_7_0, Version(5, 7, 99)},
           {v5_7_0, before_version(version_check.first)},
           {version_check.first, after_version(version_check.first)}});
    }
  }

  // Check is only available if > 8.0
  test_check_availability(ids::k_invalid_privileges_check, false,
                          {{v5_7_0, Version(5, 7, 99)},
                           {v5_7_0, Version(8, 4, 0)},
                           {v5_7_0, Version(8, 4, 1)}});

  test_check_availability(ids::k_invalid_privileges_check, true,
                          {{Version(8, 0, 11), Version(8, 4, 0)},
                           {Version(8, 0, 11), Version(8, 4, 1)}});

  const auto &vShell = mysqlshdk::utils::k_shell_version;
  test_check_availability(ids::k_changed_functions_generated_columns_check,
                          true,
                          {{Version(5, 6, 0), v5_7_0},
                           {Version(8, 0, 27), Version(8, 0, 28)},
                           {Version(5, 6, 0), vShell}});

  test_check_availability(ids::k_changed_functions_generated_columns_check,
                          false,
                          {{Version(5, 6, 8), Version(5, 6, 9)},
                           {v5_7_0, Version(8, 0, 27)},
                           {Version(8, 0, 28), vShell}});

  test_check_availability(ids::k_deprecated_default_auth_check, true,
                          {{v5_7_0, Version(8, 0, 0)},
                           {Version(8, 0, 33), Version(8, 1, 0)},
                           {Version(8, 1, 0), Version(8, 2, 0)},
                           {v5_7_0, vShell}});

  test_check_availability(ids::k_deprecated_default_auth_check, false,
                          {{v5_7_0, Version(5, 7, 44)},
                           {Version(8, 0, 0), Version(8, 0, 99)},
                           {Version(8, 1, 0), Version(8, 1, 99)},
                           {Version(8, 2, 0), vShell}});

  test_check_availability(ids::k_deprecated_router_auth_method_check, true,
                          {{v5_7_0, Version(8, 0, 0)},
                           {Version(8, 0, 33), Version(8, 1, 0)},
                           {Version(8, 1, 0), Version(8, 2, 0)},
                           {v5_7_0, vShell}});

  test_check_availability(ids::k_deprecated_router_auth_method_check, false,
                          {{v5_7_0, Version(5, 7, 44)},
                           {Version(8, 0, 0), Version(8, 0, 99)},
                           {Version(8, 1, 0), Version(8, 1, 99)},
                           {Version(8, 2, 0), vShell}});

  // checkTableCommand is available all the time as it has no conditions
  test_check_availability(ids::k_table_command_check, true,
                          {{v5_7_0, after_version(v5_7_0)},
                           {before_version(vShell), vShell},
                           {vShell, after_version(vShell)}});

  // Avaliable on these conditions for non WIN
  test_check_availability(ids::k_fts_in_tablename_check, true,
                          {{v5_7_0, Version(8, 0, 11)},
                           {Version(8, 0, 10), Version(8, 0, 11)},
                           {v5_7_0, Version(8, 0, 17)}});

  // Unavailable on the same conditions if WIN
  test_check_availability(ids::k_fts_in_tablename_check, false,
                          {{v5_7_0, Version(8, 0, 11)},
                           {Version(8, 0, 10), Version(8, 0, 11)},
                           {v5_7_0, Version(8, 0, 17)}},
                          "", "WIN");

  // Unavailable if reaches 8.0.18
  test_check_availability(
      ids::k_fts_in_tablename_check, false,
      {{v5_7_0, Version(8, 0, 18)}, {v5_7_0, Version(8, 0, 19)}});

  test_check_availability(ids::k_partitioned_tables_in_shared_tablespaces_check,
                          true,
                          {{v5_7_0, Version(8, 0, 11)},
                           {Version(8, 0, 12), Version(8, 0, 13)},
                           {Version(5, 7, 0), vShell}});

  test_check_availability(ids::k_partitioned_tables_in_shared_tablespaces_check,
                          false,
                          {{v5_7_0, Version(8, 0, 10)},
                           {Version(8, 0, 11), Version(8, 0, 12)},
                           {Version(8, 0, 13), vShell}});

  test_check_availability(ids::k_reserved_keywords_check, true,
                          {{v5_7_0, Version(8, 0, 11)},
                           {Version(8, 0, 11), Version(8, 0, 14)},
                           {Version(8, 0, 14), Version(8, 0, 17)},
                           {Version(8, 0, 17), Version(8, 0, 31)}});

  test_check_availability(ids::k_reserved_keywords_check, false,
                          {{v5_7_0, Version(8, 0, 10)},
                           {Version(8, 0, 11), Version(8, 0, 13)},
                           {Version(8, 0, 14), Version(8, 0, 16)},
                           {Version(8, 0, 17), Version(8, 0, 30)},
                           {Version(8, 0, 31), vShell}});

  // syntax_check:
  // available: always between series versions
  {
    std::vector<std::pair<Version, Version>> version_checks;
    // bugfix series
    for (auto patch = 0; patch <= k_latest_versions["8.0"].get_patch();
         patch++) {
      version_checks.emplace_back(v5_7_0, Version(8, 0, patch));
      for (auto patch2 = 0; patch2 <= k_latest_versions["8.4"].get_patch();
           patch2++) {
        /*
         * std::make_pair here is intentional - in some cases on some compilers,
         * it fails to deduce the correct template instantiation for this type,
         * and reports a "maybe uninitialized" warning inside the std::string
         * type. std::make_pair tells the compiler directly, how to instance
         * this template correctly.
         */
        version_checks.emplace_back(
            std::make_pair(Version(8, 0, patch), Version(8, 4, patch2)));
      }
    }
    for (auto major = 9; major <= mysqlshdk::utils::k_shell_version.get_major();
         major++) {
      // series innovation
      for (auto minor =
               mysqlshdk::utils::get_first_innovation_version(Version(major, 0))
                   .get_minor();
           minor < mysqlshdk::utils::get_first_lts_version(Version(major, 0))
                       .get_minor();
           minor++) {
        // between bugfix and innovation
        for (auto patch = 0; patch <= k_latest_versions["8.0"].get_patch();
             patch++) {
          version_checks.emplace_back(
              std::make_pair(Version(8, 0, patch), Version(major, minor)));
        }
        // between lts and innovation
        for (auto patch = 0; patch <= k_latest_versions["8.4"].get_patch();
             patch++) {
          version_checks.emplace_back(
              std::make_pair(Version(8, 4, patch), Version(major, minor)));
        }
        // between innovation and same series lts
        for (auto lts_minor =
                 mysqlshdk::utils::get_first_lts_version(Version(major, 0))
                     .get_minor();
             lts_minor < 10; lts_minor++) {
          version_checks.emplace_back(
              std::make_pair(Version(major, minor), Version(major, lts_minor)));
        }
      }
      // series lts
      for (auto minor =
               mysqlshdk::utils::get_first_lts_version(Version(major, 0))
                   .get_minor();
           minor < 10; minor++) {
        // between lts and next series innovation
        for (auto inno_minor = mysqlshdk::utils::get_first_innovation_version(
                                   Version(major + 1, 0))
                                   .get_minor();
             inno_minor <
             mysqlshdk::utils::get_first_lts_version(Version(major + 1, 0))
                 .get_minor();
             inno_minor++) {
          version_checks.emplace_back(std::make_pair(
              Version(major, minor), Version(major + 1, inno_minor)));
        }
        // between lts and next series lts
        for (auto lts_minor =
                 mysqlshdk::utils::get_first_lts_version(Version(major + 1, 0))
                     .get_minor();
             lts_minor < 10; lts_minor++) {
          version_checks.emplace_back(std::make_pair(
              Version(major, minor), Version(major + 1, lts_minor)));
        }
      }
    }
    test_check_availability(ids::k_syntax_check, true, version_checks);
  }
  // not available: on the same series, different patch
  {
    std::vector<std::pair<Version, Version>> version_checks;
    // bugfix series
    for (auto patch = 0; patch <= k_latest_versions["8.0"].get_patch();
         patch++) {
      for (auto patch2 = patch + 1;
           patch2 <= k_latest_versions["8.0"].get_patch(); patch2++) {
        version_checks.emplace_back(
            std::make_pair(Version(8, 0, patch), Version(8, 0, patch2)));
      }
    }
    for (auto patch = 0; patch <= k_latest_versions["8.4"].get_patch();
         patch++) {
      for (auto patch2 = patch + 1;
           patch2 <= k_latest_versions["8.4"].get_patch(); patch2++) {
        version_checks.emplace_back(
            std::make_pair(Version(8, 4, patch), Version(8, 4, patch2)));
      }
    }
    // other
    for (auto major = 9; major <= mysqlshdk::utils::k_shell_version.get_major();
         major++) {
      for (auto minor = 0; minor < 10; minor++) {
        auto it = k_latest_versions.find(Version(major, minor).get_short());
        if (it == k_latest_versions.end()) {
          continue;
        }
        auto latest = it->second;
        for (auto patch = 0; patch <= latest.get_patch(); patch++) {
          for (auto patch2 = patch + 1; patch2 <= latest.get_patch();
               patch2++) {
            version_checks.emplace_back(std::make_pair(
                Version(major, minor, patch), Version(major, minor, patch2)));
          }
        }
      }
    }
    test_check_availability(ids::k_syntax_check, false, version_checks);
  }

  // Check unavaliability impossible due to present deprecated sysvars that ware
  // never removed
  test_check_availability(ids::k_sys_vars_check, true, {{v5_7_0, vShell}});

  // spatialIndex check:
  // Should be available only from source server version 8.0.3 and
  // before 8.0.41/8.4.4/9.2.0 Also should be available only after/equal target
  // version 8.0.41/8.4.4/9.2.0
  {
    std::vector<std::pair<Version, Version>> version_checks_true;
    std::vector<std::pair<Version, Version>> version_checks_false;

    const auto patch_8_0_bug = Version(
        8, 0, 3);  // from what patch the spatial corruption could happen
    const auto patch_8_0_fix =
        Version(8, 0, 41);  // what patch fix was implemented for 8 series
    const auto patch_8_4_fix =
        Version(8, 4, 4);  // what patch fix was implemented for 8.4 series
    const auto patch_9_fix =
        Version(9, 2, 0);  // what minor fix was implemented for 9 series

    auto target_check = [&](const Version &version) {
      if (version >= patch_9_fix) return true;
      if (version >= Version(9, 0, 0)) return false;
      if (version >= patch_8_4_fix) return true;
      if (version >= Version(8, 1, 0)) return false;
      if (version >= patch_8_0_fix) return true;
      return false;
    };

    auto should_be_available = [&](const Version &source_version,
                                   const Version &target_version) {
      if (patch_8_0_bug > source_version) {
        return false;
      }
      if (source_version < patch_8_0_fix) {
        return target_check(target_version);
      }
      if (Version(8, 1, 0) <= source_version &&
          source_version < patch_8_4_fix) {
        return target_check(target_version);
      }
      if (Version(9, 0, 0) <= source_version && source_version < patch_9_fix) {
        return target_check(target_version);
      }

      return false;
    };

    auto add_checks = [&](const Version &source_version,
                          const Version &target_version) {
      if (source_version > target_version) {
        return;
      }
      if (should_be_available(source_version, target_version)) {
        version_checks_true.emplace_back(source_version, target_version);
      } else {
        version_checks_false.emplace_back(source_version, target_version);
      }
    };

    auto add_target_checks = [&](const Version &source_version) {
      for (auto target_patch = 0;
           target_patch <= k_latest_versions["8.0"].get_patch();
           target_patch++) {
        add_checks(source_version, Version(8, 0, target_patch));
      }
      for (auto target_patch = 0;
           target_patch < k_latest_versions["8.4"].get_patch();
           target_patch++) {
        add_checks(source_version, Version(8, 4, target_patch));
      }
      for (auto target_minor = 0;
           target_minor < k_latest_versions["9"].get_minor(); target_minor++) {
        add_checks(source_version, Version(9, target_minor));
      }
    };

    for (auto patch = 0; patch <= k_latest_versions["8.0"].get_patch();
         patch++) {
      add_target_checks(Version(8, 0, patch));
    }
    for (auto patch = 0; patch <= k_latest_versions["8.4"].get_patch();
         patch++) {
      add_target_checks(Version(8, 4, patch));
    }
    for (auto minor = 0; minor <= k_latest_versions["9"].get_minor(); minor++) {
      add_target_checks(Version(9, minor));
    }

    test_check_availability(ids::k_spatial_index, true, version_checks_true);
    test_check_availability(ids::k_spatial_index, false, version_checks_false);
  }

  // sanity checks
  test_check_availability(ids::k_spatial_index, true,
                          {
                              {Version(8, 0, 11), Version(8, 0, 41)},
                              {Version(8, 0, 23), Version(9, 3, 0)},
                              {Version(8, 4, 1), Version(8, 4, 4)},
                              {Version(9, 1, 0), Version(9, 3, 0)},
                          });
  test_check_availability(ids::k_spatial_index, false,
                          {
                              {Version(8, 0, 0), Version(8, 0, 41)},
                              {Version(8, 0, 3), Version(8, 0, 39)},
                              {Version(8, 0, 24), Version(8, 4, 2)},
                              {Version(9, 0, 0), Version(9, 1, 0)},
                          });

  // authentication_fido:
  // Introduced: 8.0.27
  // Deprecated: 8.2.0
  // Removed: 8.4.0
  // Any version starting after the feature was introduced but before the
  // feature was removed, independently of the end version
  test_check_availability(
      ids::k_auth_method_usage_check, true,
      {{Version(8, 0, 27), Version(8, 0, 28)},  // Start on feature introduction
       {Version(8, 0, 28),
        Version(8, 0, 29)},  // Start after feature introduction
       {Version(8, 2, 0), Version(8, 2, 1)},  // Start on feature deprecation
       {Version(8, 2, 1),
        Version(8, 2, 2)}},  // Start after feature deprecation
      "authentication_fido");

  test_check_availability(
      ids::k_auth_method_usage_check, false,
      {{Version(8, 0, 25),
        Version(8, 0, 26)},  // Start and end before the feature
       {Version(8, 0, 26), Version(8, 0, 27)},  // Start before the feature, end
                                                // when feature was introduced
       {Version(8, 0, 26), Version(8, 0, 28)},  // Start before the feature, end
                                                // after feature was introduced
       {Version(8, 4, 0), Version(8, 4, 1)},    // Start on feature removal
       {Version(8, 4, 0), Version(8, 4, 1)}},   // Start after feature removal
      "authentication_fido");

  // sha256_password and "mysql_native_password"
  // Since no start date is defined for these, they are included all the time
  // before the feature is removed
  for (const auto &method : {"sha256_password", "mysql_native_password"}) {
    test_check_availability(
        ids::k_auth_method_usage_check, true,
        {{Version(5, 7, 44),
          Version(8, 0, 0)},  // Start before feature deprecation
         {Version(8, 0, 0), Version(8, 0, 33)},  // Start on feature deprecation
         {Version(8, 0, 33),
          Version(8, 0, 34)},  // Start after feature deprecation
         {Version(8, 3, 0), Version(8, 4, 0)}},  // Start before feature removal
        method);
  }

  // Not included after feature removal
  test_check_availability(
      ids::k_auth_method_usage_check, false,
      {{Version(9, 0, 0), Version(9, 1, 0)},   // Start on feature removal
       {Version(9, 1, 0), Version(9, 2, 0)}},  // Start on feature deprecation
      "mysql_native_password");

  // authentication_fido:
  // Introduced: 8.0.27
  // Deprecated: 8.2.0
  // Removed: 8.4.0
  // Any version starting after the feature was introduced but before the
  // feature was removed, independently of the end version
  test_check_availability(
      ids::k_plugin_usage_check, true,
      {{Version(8, 0, 27), Version(8, 0, 28)},  // Start on feature introduction
       {Version(8, 0, 28),
        Version(8, 0, 29)},  // Start after feature introduction
       {Version(8, 2, 0), Version(8, 2, 1)},  // Start on feature deprecation
       {Version(8, 2, 1),
        Version(8, 2, 2)}},  // Start after feature deprecation
      "authentication_fido");

  test_check_availability(
      ids::k_plugin_usage_check, false,
      {{Version(8, 0, 25),
        Version(8, 0, 26)},  // Start and end before the feature
       {Version(8, 0, 26), Version(8, 0, 27)},  // Start before the feature, end
                                                // when feature was introduced
       {Version(8, 0, 26), Version(8, 0, 28)},  // Start before the feature, end
                                                // after feature was introduced
       {Version(8, 4, 0), Version(8, 4, 1)},    // Start on feature removal
       {Version(8, 4, 0), Version(8, 4, 1)}},   // Start after feature removal
      "authentication_fido");

  for (const auto &method : {"keyring_file", "keyring_encrypted_file"}) {
    test_check_availability(
        ids::k_plugin_usage_check, true,
        {{Version(5, 7, 44),
          Version(8, 0, 34)},  // Start before feature deprecation
         {Version(8, 0, 34),
          Version(8, 0, 35)},  // Start on feature deprecation
         {Version(8, 0, 35),
          Version(8, 0, 36)},  // Start after feature deprecation
         {Version(8, 3, 0), Version(8, 4, 0)}},  // Start before feature removal
        method);

    // Not included after feature removal
    test_check_availability(
        ids::k_plugin_usage_check, false,
        {{Version(8, 4, 0), Version(8, 4, 1)},   // Start on feature removal
         {Version(8, 4, 1), Version(8, 4, 2)}},  // Start on feature deprecation
        method);
  }

  test_check_availability(
      ids::k_plugin_usage_check, true,
      {{Version(5, 7, 44),
        Version(8, 0, 31)},  // Start before feature deprecation
       {Version(8, 0, 31), Version(8, 0, 35)},  // Start on feature deprecation
       {Version(8, 0, 35),
        Version(8, 0, 36)},  // Start after feature deprecation
       {Version(8, 3, 0), Version(8, 4, 0)}},  // Start before feature removal
      "keyring_oci");

  // Not included after feature removal
  test_check_availability(
      ids::k_plugin_usage_check, false,
      {{Version(8, 4, 0), Version(8, 4, 1)},   // Start on feature removal
       {Version(8, 4, 1), Version(8, 4, 2)}},  // Start on feature deprecation
      "keyring_oci");

  // Not included in upgrades that don't cross 8.4.0
  for (const auto &item : {"admin_ssl_cipher", "admin_tls_ciphersuites"}) {
    test_check_availability(ids::k_sys_vars_check, false,
                            {{Version(8, 0, 0), Version(8, 1, 0)},
                             {Version(8, 2, 0), Version(8, 3, 0)}},
                            item, "", "allowedValues");
    // Included in upgrades that get/cross 8.4.0
    test_check_availability(ids::k_sys_vars_check, true,
                            {{Version(8, 3, 0), Version(8, 4, 0)},
                             {Version(8, 4, 0), Version(8, 4, 1)}},
                            item, "", "allowedValues");
  }

  test_check_availability(ids::k_foreign_key_references, true,
                          {{v5_7_0, Version(8, 4, 0)},
                           {v5_7_0, Version(8, 4, 1)},
                           {v5_7_0, Version(9, 0, 0)},
                           {Version(8, 0, 37), Version(8, 4, 0)},
                           {Version(8, 0, 37), Version(8, 4, 1)},
                           {Version(8, 0, 37), Version(9, 0, 0)},
                           {Version(8, 4, 0), Version(9, 0, 0)},
                           {Version(8, 4, 1), Version(9, 0, 0)},
                           {Version(9, 0, 0), Version(9, 1, 0)}});

  test_check_availability(
      ids::k_foreign_key_references, false,
      {{v5_7_0, Version(8, 3, 0)}, {Version(8, 0, 37), Version(8, 3, 0)}});
  {
    // Tests without positive verification are marked as done
    positive.insert(ids::k_innodb_rowformat_check);  // Not used
    positive.insert(ids::k_sys_vars_check);

    std::set<std::string_view> missing;
    std::set_difference(ids::all.begin(), ids::all.end(), positive.begin(),
                        positive.end(), std::inserter(missing, missing.end()));

    if (!missing.empty()) {
      SCOPED_TRACE(shcore::str_format(
          "Missing positive check availability tests for: %s",
          shcore::str_join(missing, ", ").c_str()));
      EXPECT_TRUE(false);
    }
  }

  {
    // Tests without negative verification are marked as done
    negative.insert(ids::k_table_command_check);     // Always enabled
    negative.insert(ids::k_innodb_rowformat_check);  // Not used
    negative.insert(ids::k_sys_vars_check);

    std::set<std::string_view> missing;
    std::set_difference(ids::all.begin(), ids::all.end(), negative.begin(),
                        negative.end(), std::inserter(missing, missing.end()));

    if (!missing.empty()) {
      SCOPED_TRACE(shcore::str_format(
          "Missing negative check availability tests for: %s",
          shcore::str_join(missing, ", ").c_str()));
      EXPECT_TRUE(false);
    }
  }
}

}  // namespace upgrade_checker
}  // namespace mysqlsh
