/*
 * Copyright (c) 2020, 2026, 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 MODULES_UTIL_DUMP_SCHEMA_DUMPER_H_
#define MODULES_UTIL_DUMP_SCHEMA_DUMPER_H_

#include <functional>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <unordered_set>
#include <utility>
#include <vector>

#include "mysqlshdk/libs/db/filtering_options.h"
#include "mysqlshdk/libs/db/result.h"
#include "mysqlshdk/libs/db/session.h"
#include "mysqlshdk/libs/storage/ifile.h"
#include "mysqlshdk/libs/utils/utils_general.h"
#include "mysqlshdk/libs/utils/version.h"

#include "modules/util/dump/compatibility.h"
#include "modules/util/dump/compatibility_issue.h"
#include "modules/util/dump/instance_cache.h"

namespace mysqlsh {
namespace dump {

class Schema_dumper {
 public:
  using Filtering_options = mysqlshdk::db::Filtering_options;
  using IFile = mysqlshdk::storage::IFile;

  struct User_statements {
    enum class Type {
      UNKNOWN,
      CREATE_USER,
      GRANT,
      DEFAULT_ROLE,
    };

    Type type = Type::UNKNOWN;
    std::string account;
    std::vector<std::string> statements;
  };

  Schema_dumper(const std::shared_ptr<mysqlshdk::db::ISession> &mysql,
                const Instance_cache &cache);

  static std::vector<User_statements> preprocess_users_script(
      const std::string &script,
      const std::function<bool(const std::string &)> &include_user_cb,
      const std::function<bool(const std::string &, const std::string &)>
          &strip_revoked_privilege_cb = {});

  void dump_all_tablespaces_ddl(IFile *file);
  void dump_tablespaces_ddl_for_dbs(IFile *file,
                                    const std::vector<std::string> &dbs);
  void dump_tablespaces_ddl_for_tables(IFile *file, const std::string &db,
                                       const std::vector<std::string> &tables);

  std::vector<Compatibility_issue> dump_schema_ddl(IFile *file,
                                                   const std::string &db);
  std::vector<Compatibility_issue> dump_table_ddl(IFile *file,
                                                  const std::string &db,
                                                  const std::string &table);
  void dump_temporary_view_ddl(IFile *file, const std::string &db,
                               const std::string &view);
  std::vector<Compatibility_issue> dump_view_ddl(IFile *file,
                                                 const std::string &db,
                                                 const std::string &view);
  int count_triggers_for_table(const std::string &db, const std::string &table);
  std::vector<Compatibility_issue> dump_triggers_for_table_ddl(
      IFile *file, const std::string &db, const std::string &table);
  const std::vector<std::string> &get_triggers(const std::string &db,
                                               const std::string &table);

  std::vector<Compatibility_issue> dump_events_ddl(IFile *file,
                                                   const std::string &db);
  const std::unordered_set<std::string> &get_events(const std::string &db);

  std::vector<Compatibility_issue> dump_routines_ddl(IFile *file,
                                                     const std::string &db);
  std::vector<std::string> get_routines(const std::string &db,
                                        const std::string &type);
  const std::vector<Instance_cache::Routine::Library_reference> &
  get_routine_dependencies(const std::string &db, const std::string &routine,
                           const std::string_view type);

  std::vector<Compatibility_issue> dump_libraries_ddl(IFile *file,
                                                      const std::string &db);
  const std::unordered_set<std::string> &get_libraries(const std::string &db);

  std::vector<Compatibility_issue> dump_grants(IFile *file);

  void dump_data_masking_policies(IFile *file);

  const std::vector<Instance_cache::Histogram> &get_histograms(
      const std::string &db_name, const std::string &table_name);

  // Must be called for each file
  void write_header(IFile *sql_file);
  void write_footer(IFile *sql_file);

  void write_comment(IFile *sql_file, const std::string &db_name = "",
                     const std::string &table_name = "");

  void use_filters(const Filtering_options *filters) { m_filters = filters; }
  void use_shared_session(mysqlshdk::db::Shared_session shared_session) {
    m_shared_session = std::move(shared_session);
  }

  bool partial_revokes() const;

 public:
  // Config options
  void set_target_version(const mysqlshdk::utils::Version &target_version);

  bool opt_force = false;
  bool opt_flush_privileges = false;
  bool opt_drop_database = false;
  bool opt_drop_table = false;
  bool opt_drop_view = false;
  bool opt_drop_event = true;
  bool opt_drop_routine = true;
  bool opt_drop_library = true;
  bool opt_drop_trigger = true;
  bool opt_reexecutable = true;
  bool opt_create_options = true;
  bool opt_databases = false;
  bool opt_alldbs = false;
  bool opt_create_db = false;
  bool opt_set_charset = true;
  bool opt_single_transaction = false;
  bool opt_comments = true;
  bool opt_compact = false;
  bool opt_tz_utc = true;
  bool opt_ansi_mode = false;  ///< Force the "ANSI" SQL_MODE.
  bool opt_column_statistics = false;
  bool opt_mysqlaas = false;
  bool opt_report_deprecated_errors_as_warnings = false;
  bool opt_force_innodb = false;
  bool opt_strip_directory = false;
  bool opt_strip_restricted_grants = false;
  bool opt_strip_tablespaces = false;
  bool opt_strip_definer = false;
  bool opt_lock_invalid_accounts = false;
  bool opt_skip_invalid_accounts = false;
  bool opt_ignore_missing_pks = false;
  bool opt_create_invisible_pks = false;
  bool opt_strip_invalid_grants = false;
  bool opt_ignore_wildcard_grants = false;
  bool opt_unescape_wildcard_grants = false;
  bool opt_target_has_mysql_native_password = false;
  std::string opt_character_set_results = "utf8mb4";

  enum enum_set_gtid_purged_mode {
    SET_GTID_PURGED_OFF = 0,
    SET_GTID_PURGED_AUTO = 1,
    SET_GTID_PURGED_ON = 2,
    SET_GTID_PURGED_COMMENTED = 3
  } opt_set_gtid_purged_mode = SET_GTID_PURGED_AUTO;

 private:
#ifdef FRIEND_TEST
  FRIEND_TEST(Schema_dumper_test, check_object_for_definer);
  FRIEND_TEST(Schema_dumper_test, check_object_for_definer_set_any_definer);
  FRIEND_TEST(Schema_dumper_test,
              check_object_for_definer_set_any_definer_issues);
  FRIEND_TEST(Schema_dumper_test, strip_restricted_grants_set_any_definer);
  FRIEND_TEST(Schema_dumper_test, include_grant);
#endif  // FRIEND_TEST

  struct Version_dependent_check {
    bool supported = false;

    struct {
      bool report_errors = false;
      bool downgrade_errors = false;
    } deprecated;
  };

  int execute_no_throw(const std::string &s,
                       mysqlshdk::db::Error *out_error = nullptr);

  int execute_maybe_throw(const std::string &s,
                          mysqlshdk::db::Error *out_error = nullptr);

  int query_no_throw(const std::string &s,
                     std::shared_ptr<mysqlshdk::db::IResult> *out_result,
                     mysqlshdk::db::Error *out_error = nullptr);

  std::shared_ptr<mysqlshdk::db::IResult> query_log_and_throw(
      const std::string &s);

  std::shared_ptr<mysqlshdk::db::IResult> query_log_error(
      const std::string &sql, const std::string &schema,
      const std::string &table) const;

  int query_with_binary_charset(
      const std::string &s, std::shared_ptr<mysqlshdk::db::IResult> *out_result,
      mysqlshdk::db::Error *out_error = nullptr,
      std::vector<std::unique_ptr<mysqlshdk::db::Warning>> *out_warnings =
          nullptr);

  void fetch_db_collation(const std::string &db, std::string *out_db_cl_name,
                          bool replace_unknown_collation = true);

  void switch_character_set_results(const char *cs_name);

  void use(const std::string &db) const;

  void unescape(IFile *file, std::string_view s);

  std::string quote_for_like(const std::string &name_);

  /* A common printing function for xml and non-xml modes. */

  void print_comment(IFile *sql_file, bool is_error, const char *format, ...);

  char *create_delimiter(const std::string &query, char *delimiter_buff,
                         const int delimiter_max_size);

  std::vector<Compatibility_issue> dump_events_for_db(IFile *sql_file,
                                                      const std::string &db);

  std::vector<Compatibility_issue> dump_routines_for_db(IFile *sql_file,
                                                        const std::string &db);

  std::vector<Compatibility_issue> dump_libraries_for_db(IFile *sql_file,
                                                         const std::string &db);

  std::vector<Compatibility_issue> check_ct_for_mysqlaas(
      const std::string &db, const std::string &table,
      std::string *create_table);

  void check_object_for_definer(Compatibility_issue::Object_type object,
                                const std::string &qualified_name,
                                std::string *ddl,
                                std::vector<Compatibility_issue> *issues);

  std::vector<Compatibility_issue> get_table_structure(
      IFile *sql_file, const std::string &table, const std::string &db,
      std::string *out_table_type, char *ignore_flag);

  std::vector<Compatibility_issue> dump_trigger(
      IFile *sql_file,
      const std::shared_ptr<mysqlshdk::db::IResult> &show_create_trigger_rs,
      const std::string &db_name, const std::string &table_name,
      const std::string &trigger_name, const std::string &db_collation_name);

  std::vector<Compatibility_issue> dump_triggers_for_table(
      IFile *sql_file, const std::string &table, const std::string &db);
  void dump_column_statistics_for_table(IFile *sql_file,
                                        const std::string &table_name,
                                        const std::string &db_name);

  std::vector<Compatibility_issue> dump_table(IFile *sql_file,
                                              const std::string &table,
                                              const std::string &db);

  int dump_all_tablespaces(IFile *file);
  int dump_tablespaces_for_databases(IFile *sql_file,
                                     const std::vector<std::string> &databases);
  int dump_tablespaces_for_tables(IFile *file, const std::string &db,
                                  const std::vector<std::string> &table_names);

  int dump_tablespaces(IFile *sql_file, const std::string &ts_where);

  int is_ndbinfo(const std::string &dbname);

  int init_dumping_views(IFile *sql_file, const char * /*qdatabase*/);

  int init_dumping(IFile *sql_file, const std::string &database,
                   const std::function<int(IFile *, const char *)> &init_func);

  std::vector<std::string> get_table_names();

  std::string get_actual_table_name(const std::string &old_table_name);

  char check_if_ignore_table(const std::string &db,
                             const std::string &table_name,
                             std::string *out_table_type);

  void set_session_binlog(IFile *sql_file, bool flag);

  bool add_set_gtid_purged(IFile *sql_file);

  bool process_set_gtid_purged(IFile *sql_file);
  std::vector<Compatibility_issue> get_view_structure(IFile *sql_file,
                                                      const std::string &table,
                                                      const std::string &db);

  std::string expand_all_privileges(const std::string &stmt,
                                    const std::string &grantee,
                                    std::string *out_schema);

  Version_dependent_check set_any_definer_check() const;

  std::vector<std::string> strip_restricted_grants(
      const std::string &user, std::string *ddl,
      std::vector<Compatibility_issue> *issues) const;

  void check_view_for_table_references(
      const std::string &db, const std::string &name,
      bool has_invalid_definition,
      std::vector<Compatibility_issue> *issues) const;

  void check_routine_for_dependencies(
      const std::string &db, const std::string &name,
      Compatibility_issue::Object_type type,
      std::vector<Compatibility_issue> *issues) const;

  bool is_library_included(const std::string &schema,
                           const std::string &library) const;

  std::size_t column_count(const std::string &schema,
                           const std::string &table) const;

  // connections to the instance
  std::shared_ptr<mysqlshdk::db::ISession> m_mysql;
  mysqlshdk::db::Shared_session m_shared_session;

  // cache and filtering
  const Instance_cache &m_cache;
  const Filtering_options *m_filters = nullptr;

  // whether a warning that users are not being dumped has been reported
  bool m_users_not_dumped_reported = false;

  // version information
  mysqlshdk::utils::Version m_target_version;
  bool m_supports_set_any_definer_privilege = false;
  bool m_supports_pke_as_pk = false;

  // leftovers from original code
  bool is_binlog_disabled = false;
  bool stats_tables_included = false;
};

}  // namespace dump
}  // namespace mysqlsh

#endif  // MODULES_UTIL_DUMP_SCHEMA_DUMPER_H_
