# typed: strict
class Domain::Fa::UserEnqueuer
  extend T::Sig
  extend T::Helpers

  include HasBulkEnqueueJobs
  include HasColorLogger
  include HasMeasureDuration
  include Domain::Fa::HasCountFailedInQueue

  sig do
    params(
      start_at: Integer,
      low_water_mark: Integer,
      high_water_mark: Integer,
    ).void
  end
  def initialize(start_at:, low_water_mark:, high_water_mark:)
    @low_water_mark = low_water_mark
    @high_water_mark = high_water_mark
    raise if @high_water_mark <= @low_water_mark
    @user_iterator =
      T.let(
        Enumerator.new do |e|
          Domain::User::FaUser.find_each(start: start_at) { |user| e << user }
        end,
        T::Enumerator[Domain::User::FaUser],
      )
  end

  sig { returns(T.nilable(Symbol)) }
  def run_once
    already_enqueued = enqueued_count
    if already_enqueued <= @low_water_mark
      to_enqueue = @high_water_mark - already_enqueued
      logger.info(
        "enqueuing #{to_enqueue.to_s.bold} more users - #{already_enqueued.to_s.bold} already enqueued",
      )
      rows =
        measure("gathered #{to_enqueue.to_s.bold} users to enqueue") do
          to_enqueue.times.map { @user_iterator.next }
        end
      measure("enqueue jobs") do
        rows.each do |user|
          types = []
          if user.state == "ok"
            if user.favs_scan.due?
              Domain::Fa::Job::FavsJob.perform_later({ user: user })
              types << "favs"
            end
            if user.page_scan.due?
              Domain::Fa::Job::UserPageJob.perform_later({ user: user })
              types << "page"
            end

            if user.gallery_scan.due?
              Domain::Fa::Job::UserGalleryJob.perform_later({ user: user })
              types << "gallery"
            end
            if user.follows_scan.due?
              Domain::Fa::Job::UserFollowsJob.perform_later({ user: user })
              types << "follows"
            end
          end

          avatar = user.avatar || user.create_avatar!
          if avatar.log_entry.nil? && avatar.state_pending?
            Domain::Fa::Job::UserAvatarJob.perform_later({ avatar: avatar })
            types << "avatar"
          end

          types = types.map { |t| t.bold }.join("|")
          logger.info "#{types} - #{user.url_name&.bold} - #{user.id.to_s.bold}"
        end
      end
      throw StopIteration if rows.empty?
    else
      logger.info(
        "#{already_enqueued.to_s.bold} already enqueued (max #{@high_water_mark.to_s.bold}) - " +
          "waiting to fall below #{@low_water_mark.to_s.bold}",
      )
      :sleep
    end
  end

  private

  sig { returns(Integer) }
  def enqueued_count
    if Rails.env.test?
      return SpecUtil.enqueued_jobs(Domain::Fa::Job::UserFollowsJob).count
    end

    T.must(
      %w[
        fa_post
        fa_user_avatar
        fa_user_favs
        fa_user_follows
        fa_user_gallery
        fa_user_page
        static_file
      ].map { |queue_name| count_failed_in_queue(queue_name) }.max,
    )
  end
end
