# typed: strict
class Domain::Fa::Job::UserGalleryJob < Domain::Fa::Job::Base
  queue_as :fa_user_gallery
  queue_with_priority do
    T.bind(self, Domain::Fa::Job::UserGalleryJob)
    user_from_args!.scanned_gallery_at.nil? ? -13 : -3
  end

  MAX_PAGE_NUMBER = 350
  LISTINGS_PER_PAGE_THRESHOLD = 72

  class Folder < T::ImmutableStruct
    include T::Struct::ActsAsComparable

    const :href, String
    const :title, String
  end

  sig { params(args: T.untyped).void }
  def initialize(*args)
    super(*T.unsafe(args))
    @go_until_end = T.let(false, T::Boolean)
    @max_page_number = T.let(MAX_PAGE_NUMBER, Integer)
    @visited = T.let(Set.new, T::Set[Folder])
    @folders = T.let(Set.new, T::Set[Folder])
  end

  sig { override.params(args: T::Hash[Symbol, T.untyped]).returns(T.untyped) }
  def perform(args)
    user = user_from_args!
    return if buggy_user?(user)

    if user.state != "ok" && user.scanned_gallery_at
      logger.warn("state == #{user.state} and already scanned, skipping")
      return
    end

    @go_until_end = user.gallery_scan.at.nil?

    if !user.gallery_scan.due? && !force_scan?
      logger.warn("gallery scanned #{user.gallery_scan.ago_in_words}, skipping")
      return
    end

    @visited = Set.new
    @folders =
      Set.new(
        [
          Folder.new(href: "/gallery/#{user.url_name}/", title: "Main Gallery"),
          Folder.new(href: "/scraps/#{user.url_name}/", title: "Scraps"),
        ],
      )

    until (@folders - @visited).empty?
      folder = (@folders - @visited).first
      @visited.add folder
      should_break = T.let(false, T::Boolean)
      logger.tagged(make_tag("folder.title", folder.title)) do
        should_break = scan_folder(user, folder) == :break
      end
      break if should_break
    end

    user.last_gallery_page_id = first_log_entry&.id
    user.scanned_gallery_at = Time.current
  ensure
    user.save! if user
  end

  private

  sig do
    params(user: Domain::User::FaUser, folder: Folder).returns(
      T.nilable(Symbol),
    )
  end
  def scan_folder(user, folder)
    page_number = 1
    total_num_new_posts_seen = 0
    total_num_posts_seen = 0
    while true
      if page_number >= @max_page_number
        fatal_error(
          "hit max #{page_number}) pages, num submissions: #{user.num_submissions}",
        )
      end

      folder_href = folder.href
      folder_href += "/" unless folder_href.end_with?("/")
      folder_href = "/" + folder_href unless folder_href.start_with?("/")
      page_url =
        "https://www.furaffinity.net#{folder_href}#{page_number}?perpage=72"
      response = http_client.get(page_url)
      fatal_error("failed to scan folder page") if response.status_code != 200

      disabled_or_not_found = user_disabled_or_not_found?(user, response)
      user.scanned_gallery_at = Time.current
      return :break if disabled_or_not_found

      page = Domain::Fa::Parser::Page.from_log_entry(response.log_entry)
      fatal_error("not a listings page") unless page.probably_listings_page?

      # newly instantiated users don't have a name yet, but can derive it from the gallery page
      user.name ||= page.user_page.name || user.url_name

      listing_page_stats =
        update_and_enqueue_posts_from_listings_page(
          ListingPageType::GalleryPage.new(page_number:, folder: folder.title),
          page_parser: page,
          for_user: user,
        )
      total_num_new_posts_seen += listing_page_stats.new_posts.count
      total_num_posts_seen += listing_page_stats.all_posts.count

      logger.info(
        format_tags(
          make_tag("page_number", page_number),
          make_tag("new on page", listing_page_stats.new_posts.count),
          make_tag("total on page", listing_page_stats.all_posts.count),
        ),
      )

      if scan_folders?
        page.submission_folders.each do |sf|
          @folders.add?(
            Folder.new(href: T.must(sf[:href]), title: T.must(sf[:title])),
          )
        end
      end

      page_number += 1
      break if listing_page_stats.new_posts.empty? && !@go_until_end
      break if listing_page_stats.all_posts.count < LISTINGS_PER_PAGE_THRESHOLD
    end

    logger.info(
      format_tags(
        "complete",
        make_tag("num_new", total_num_new_posts_seen),
        make_tag("num_total", total_num_posts_seen),
      ),
    )

    :continue
  end

  private

  sig { returns(T::Boolean) }
  def scan_folders?
    !!arguments[0][:scan_folders]
  end
end
