module Gitlab
  # Checks if a set of migrations requires downtime or not.
  class DowntimeCheck
    # The constant containing the boolean that indicates if downtime is needed
    # or not.
    DOWNTIME_CONST = :DOWNTIME

    # The constant that specifies the reason for the migration requiring
    # downtime.
    DOWNTIME_REASON_CONST = :DOWNTIME_REASON

    # Checks the given migration paths and returns an Array of
    # `Gitlab::DowntimeCheck::Message` instances.
    #
    # migrations - The migration file paths to check.
    def check(migrations)
      migrations.map do |path|
        require(path)

        migration_class = class_for_migration_file(path)

        unless migration_class.const_defined?(DOWNTIME_CONST)
          raise "The migration in #{path} does not specify if it requires " \
            "downtime or not"
        end

        if online?(migration_class)
          Message.new(path)
        else
          reason = downtime_reason(migration_class)

          unless reason
            raise "The migration in #{path} requires downtime but no reason " \
              "was given"
          end

          Message.new(path, true, reason)
        end
      end
    end

    # Checks the given migrations and prints the results to STDOUT/STDERR.
    #
    # migrations - The migration file paths to check.
    def check_and_print(migrations)
      check(migrations).each do |message|
        puts message.to_s # rubocop: disable Rails/Output
      end
    end

    # Returns the class for the given migration file path.
    def class_for_migration_file(path)
      File.basename(path, File.extname(path)).split('_', 2).last.camelize
        .constantize
    end

    # Returns true if the given migration can be performed without downtime.
    def online?(migration)
      migration.const_get(DOWNTIME_CONST) == false
    end

    # Returns the downtime reason, or nil if none was defined.
    def downtime_reason(migration)
      if migration.const_defined?(DOWNTIME_REASON_CONST)
        migration.const_get(DOWNTIME_REASON_CONST)
      else
        nil
      end
    end
  end
end
