require 'io/console'

module GitlabCtl
  class PostgreSQL
    class DecompositionMigration
      def initialize(ctl)
        @ctl = ctl
      end

      def migrate!
        unless @ctl.service_enabled?('postgresql')
          puts 'There is no PostgreSQL instance enabled in Omnibus, exiting...'
          exit 1
        end

        puts <<~MSG
        This script will migrate this GitLab instance to a two-database setup.

        WARNING:
        - This script is experimental. See https://docs.gitlab.com/ee/administration/postgresql/multiple_databases.html
        - Once migrated to a two-database setup, you cannot migrate it back.

        Ensure:
        - The new database 'gitlabhq_production_ci' has been created, for example:

        gitlab-psql -c "CREATE DATABASE gitlabhq_production_ci WITH OWNER 'gitlab'"

        - The following changes are added to /etc/gitlab/gitlab.rb configuration file
          but do **not** run 'gitlab-ctl reconfigure' yet:

        gitlab_rails['env'] = { 'GITLAB_ALLOW_SEPARATE_CI_DATABASE' => 'true' }
        gitlab_rails['databases']['ci']['enable'] = true
        gitlab_rails['databases']['ci']['db_database'] = 'gitlabhq_production_ci'

        This script will:
        - Disable background migrations because they should not be active during this migration
          See https://docs.gitlab.com/ee/development/database/batched_background_migrations.html#enable-or-disable-background-migrations
        - Stop the Gitlab Instance
        - Copy data in gitlabhq_production to gitlabhq_production_ci (by dumping, then restoring)
        - Apply configuration changes in /etc/gitlab/gitlab.rb using 'gitlab-ctl reconfigure'
        - Prevent errorneous database access
        - Re-enable background migrations
        - Restart GitLab

        This script will not:
        - Clean up data in the databases

        Please confirm the upgrade by pressing 'y':
        MSG

        prompt = $stdin.gets.chomp

        exit(1) unless prompt.casecmp('y').zero?

        disable_background_migrations unless background_migrations_initally_disabled?
        stop_gitlab_services
        run_migration
        post_migrate

        puts <<~MSG
        GitLab is now running on two databases. Data related to CI is now written to the ci
        database.

        You can also remove duplicated data by running:
        'sudo gitlab-rake gitlab:db:truncate_legacy_tables:main'
        'sudo gitlab-rake gitlab:db:truncate_legacy_tables:ci'

        MSG
      end

      private

      def background_migrations_initally_disabled?
        @background_migrations_initally_disabled ||= GitlabCtl::Util.run_command(
          'gitlab-rails runner "puts (Feature.disabled?(:execute_background_migrations, type: :ops) && Feature.disabled?(:execute_batched_migrations_on_schedule, type: :ops)).to_s"'
        ).stdout.chomp == "true"
      end

      def disable_background_migrations
        puts "Disabling Background Migrations..."
        run_command <<~CMD
        gitlab-rails runner "Feature.disable(:execute_background_migrations) && Feature.disable(:execute_batched_migrations_on_schedule)"
        CMD
      end

      def enable_background_migrations
        puts "Enabling Background Migrations..."
        run_command <<~CMD
        gitlab-rails runner "Feature.enable(:execute_background_migrations) && Feature.enable(:execute_batched_migrations_on_schedule)"
        CMD
      end

      def stop_gitlab_services
        puts "Stopping GitLab..."
        run_command("gitlab-ctl stop && gitlab-ctl start postgresql")
      end

      def run_migration
        puts "Copying data to new database..."
        run_command("gitlab-rake gitlab:db:decomposition:migrate", timeout: 84_600)
      end

      def post_migrate
        puts "Reconfigure GitLab..."
        run_command("gitlab-ctl reconfigure")
        puts "Enable write locks..."
        run_command("gitlab-rake gitlab:db:lock_writes")
        enable_background_migrations unless background_migrations_initally_disabled?
        puts "Restarting GitLab..."
        run_command("gitlab-ctl restart")
      end

      def run_command(cmd, timeout: nil)
        GitlabCtl::Util.run_command(cmd, timeout: timeout).tap do |status|
          if status.error?
            enable_background_migrations unless background_migrations_initally_disabled?

            puts status.stdout
            puts status.stderr
            puts "[ERROR] Failed to execute: #{cmd}"
            puts "This GitLab instance is still disabled."

            exit 1
          end
        end
      end
    end
  end
end
