# frozen_string_literal: true

require 'rubocop'

module Keeps
  # This is an implementation of a ::Gitlab::Housekeeper::Keep. This keep will not make any changes unless there are
  # more than one obsolete Advanced search migrations. This keep will remove all but the most recent obsolete
  # migrations from the code.
  #
  # You can run it individually with:
  #
  # ```
  # bundle exec gitlab-housekeeper -d \
  #   -k Keeps::DeleteObsoleteAdvancedSearchMigrations
  # ```
  class DeleteObsoleteAdvancedSearchMigrations < ::Gitlab::Housekeeper::Keep
    MIGRATIONS_PATH = 'ee/elastic/migrate'
    MIGRATION_REGEXP = /\A([0-9]+)_([_a-z0-9]*)\.rb\z/
    MIGRATIONS_SPECS_PATH = 'ee/spec/elastic/migrate/'
    MIGRATION_DOCS_PATH = 'ee/elastic/docs'

    GROUP_LABEL = 'group::global search'

    def initialize(...)
      @obsolete_migrations_to_delete = {}
      load_migrations_to_process

      super
    end

    def each_identified_change
      return unless obsolete_migrations_to_delete.size > 1

      change = ::Gitlab::Housekeeper::Change.new
      change.identifiers = [self.class.name.demodulize, 'remove_obsolete']
      change.context = { obsolete_migrations: obsolete_migrations_to_delete }
      yield(change)
    end

    def make_change!(change)
      create_remove_obsolete_change(change)
    end

    private

    attr_reader :obsolete_migrations_to_delete

    def load_migrations_to_process
      each_advanced_search_migration do |migration_filename, spec_filename, yaml_content|
        version = yaml_content['version']
        next unless yaml_content['obsolete']

        obsolete_migrations_to_delete[version] = { file: migration_filename, spec_file: spec_filename }
      end
    end

    def get_migration_versions_from_codebase
      migration_files = Dir[File.join(MIGRATIONS_PATH, '**', '[0-9]*_*.rb')]
      migration_versions_hash = {}
      migration_files.each do |migration_file|
        version, filename = File.basename(migration_file).scan(MIGRATION_REGEXP).first
        migration_versions_hash[version] = { version: version, filename: filename }
      end
      migration_versions_hash
    end

    def each_advanced_search_migration
      all_advanced_search_migration_files.map do |f|
        version, filename = File.basename(f).scan(MIGRATION_REGEXP).first
        yaml_file = "#{MIGRATION_DOCS_PATH}/#{version}_#{filename}.yml"
        spec_file = "#{MIGRATIONS_SPECS_PATH}/#{version}_#{filename}_spec.rb"

        yield(f, spec_file, YAML.load_file(yaml_file))
      end
    end

    def all_advanced_search_migration_files
      Dir.glob("#{MIGRATIONS_PATH}/*.rb")
    end

    def create_remove_obsolete_change(change)
      change.title = 'Remove obsolete Advanced search migrations'
      change.labels = [
        'maintenance::removal',
        GROUP_LABEL
      ]
      change.assignees = groups_helper.pick_reviewer(group_data, change.identifiers)
      change.changelog_ee = true

      # rubocop:disable Gitlab/DocumentationLinks/HardcodedUrl -- Not running inside rails application
      change.description = <<~MARKDOWN
        This migration removes all but the latest obsolete Advanced search migration files from the project.

        You can read more about the process for marking Advanced search migrations as obsolete in
        https://docs.gitlab.com/ee/development/search/advanced_search_migration_styleguide.html#deleting-advanced-search-migrations-in-a-major-version-upgrade.

        As part of our process, we want to ensure all obsolete Advanced search migrations have had at least one
        [required stop](https://docs.gitlab.com/ee/development/database/required_stops.html) as obsolete migrations
        before removing the migration code from the project. Therefore we can remove code for all Advanced search
        migrations that were made obsolete before the last required stop.

        ## Tasks to complete before merging

        - [ ] Update the archive of migrations in https://gitlab.com/gitlab-org/search-team/migration-graveyard
        - [ ] Remove references to affected migration or spec files from Rubocop TODOs
      MARKDOWN
      # rubocop:enable Gitlab/DocumentationLinks/HardcodedUrl

      change.changed_files = []
      # always leave one migration
      obsolete_migrations = change.context[:obsolete_migrations].dup
      last_key = obsolete_migrations.keys.max
      obsolete_migrations.delete(last_key)
      obsolete_migrations.each do |version, migration_data|
        FileUtils.rm_f(migration_data[:file])
        change.changed_files << migration_data[:file]

        if File.exist?(migration_data[:spec_file])
          FileUtils.rm_f(migration_data[:spec_file])
          change.changed_files << migration_data[:spec_file]
        end
      rescue StandardError => e
        warn "Error deleting #{version} migration and spec: #{e}"
        nil
      end

      change
    end

    def groups_helper
      @groups_helper ||= ::Keeps::Helpers::Groups.new
    end

    def group_data
      @group_data ||= groups_helper.group_for_group_label(GROUP_LABEL)
    end
  end
end
