# typed: strict
class Tasks::Fa::EnqueueUnscannedOkPostsTask < EnqueueJobBase
  extend T::Sig
  include HasColorLogger
  include Domain::Fa::HasCountFailedInQueue

  PROGRESS_KEY = T.let("task-fa-enqueue-unscanned-ok-posts", String)

  sig { override.returns(String) }
  def progress_key
    PROGRESS_KEY
  end

  sig do
    override
      .params(
        perform_max: T.nilable(Integer),
        start_at: T.nilable(T.any(Integer, String)),
        log_sink: T.any(IO, StringIO),
      )
      .void
  end
  def initialize(perform_max: nil, start_at: nil, log_sink: $stderr)
    super(perform_max:, log_sink:)
    @start_at = T.let(get_progress(start_at&.to_s)&.to_i, T.nilable(Integer))
  end

  sig { override.void }
  def start_enqueuing
    log("starting from fa_id: #{@start_at}") if @start_at

    query =
      Domain::Post::FaPost
        .includes(:creator, :last_submission_log_entry)
        .where(state: "ok", scanned_at: nil)
        .order(fa_id: :desc)

    query = query.where(fa_id: ..@start_at) if @start_at

    log("finding greatest qualifying fa_id...")
    greatest_post_fa_id = query.first&.fa_id

    log("counting posts...")
    pb = create_progress_bar(query.count)

    while greatest_post_fa_id
      # Check for interruption at the beginning of each batch
      break if interrupted?

      posts = query.where(fa_id: ..greatest_post_fa_id).limit(32).to_a

      # Break if no posts found
      break if posts.empty?

      hle_urls =
        posts
          .map(&:fa_id)
          .compact
          .flat_map { |fa_id| ["/view/#{fa_id}", "/view/#{fa_id}/"] }

      hle_map =
        HttpLogEntry
          .where(uri_host: "www.furaffinity.net", uri_path: hle_urls)
          .includes(:response)
          .to_a
          .map do |hle|
            fa_id = T.must(hle.uri_path&.split("/")&.last&.to_i)
            [fa_id, hle]
          end
          .to_h

      # Process posts in this batch
      posts.each do |post|
        post_desc =
          "#{(post.creator&.to_param || "(none)").rjust(20)} / #{post.to_param}".ljust(
            40,
          )
        process_post(post, hle_map)
        log("migrate post :: #{post_desc}") if pb.progress % 10 == 0
      rescue StandardError
        log("error processing post :: #{post_desc}")
      ensure
        pb.progress = [pb.progress + 1, pb.total].min
      end

      # Save progress after each batch - use the minimum fa_id from this batch
      last_processed_fa_id = posts.map(&:fa_id).compact.min
      if last_processed_fa_id
        save_progress(last_processed_fa_id.to_s)
        greatest_post_fa_id = last_processed_fa_id - 1
      else
        break
      end

      # Check for interruption after processing batch
      break if interrupted?
    end

    log("Finished processing posts")
  end

  sig { override.returns(Integer) }
  def queue_size
    count_failed_in_queue("fa_post")
  end

  private

  sig do
    params(
      post: Domain::Post::FaPost,
      hle_map: T::Hash[Integer, HttpLogEntry],
    ).void
  end
  def process_post(post, hle_map)
    fa_id = T.must(post.fa_id)
    if hle = (hle_map[fa_id] || post.guess_last_submission_log_entry)
      unless post.external_url_for_view.to_s.delete_suffix("/") ==
               hle.uri.to_s.delete_suffix("/")
        log("uri mismatch: #{post.external_url_for_view} != #{hle.uri}")
        log("post: #{post_id_str(post)}")
        log("hle: #{hle.uri_host} #{hle.uri_path} #{hle.requested_at}")
        raise("uri mismatch")
      end

      post.scanned_at = hle.requested_at&.utc
      post.last_submission_log_entry = hle
      post.posted_at = post.posted_at if post.attributes["posted_at"].nil?
      post.save!
    else
      enqueue { Domain::Fa::Job::ScanPostJob.perform_later({ post: }) }
    end
  end

  sig { params(post: Domain::Post::FaPost).returns(String) }
  def post_id_str(post)
    "#{post.to_param} / #{post.title_for_view} / #{post.creator&.to_param}"
  end
end
