# typed: strict
class Domain::Fa::BackfillTrackedObjectUserFavs
  extend T::Sig

  sig { params(user: Domain::User::FaUser).void }
  def initialize(user:)
    @user = user
  end

  class FavsGroup < T::Struct
    const :scanned_at, Time
    const :root_hle, HttpLogEntry
    const :hles, T::Array[HttpLogEntry]
  end

  class DateHelper
    extend ActionView::Helpers::DateHelper
  end
  DATE_HELPER = DateHelper

  class Stats < T::Struct
    const :total_groups, Integer
    const :total_hles, Integer
    const :total_favs, Integer
    const :total_created, Integer
  end

  sig { returns(Stats) }
  def run
    hles =
      [
        "www.furaffinity.net/favorites/#{@user.url_name}",
        "www.furaffinity.net/favorites/#{@user.url_name}/",
      ].map { |uri| HttpLogEntry.find_all_by_uri(uri).includes(:response).to_a }
        .flatten
        .sort_by(&:created_at)

    puts "loading #{hles.size} fav scan groups..."
    now = Time.now
    fav_groups =
      hles
        .map do |hle|
          # find the transitive closure of all caused_by_log_entries for this hle
          # if it's a favorites HLE
          to_search = Set.new([hle])
          visited = Set.new

          while to_search.any?
            first_hle = to_search.first
            to_search.delete(first_hle)

            # Skip if we've already processed this entry
            next if visited.include?(first_hle)
            visited.add(first_hle)

            triggered_entries =
              first_hle
                .triggered_entries
                .where("uri_path LIKE ?", "/favorites/%")
                .includes(:response)
                .to_a
            to_search.merge(triggered_entries)
          end

          print "."
          FavsGroup.new(
            scanned_at: hle.created_at,
            root_hle: hle,
            hles: visited.to_a.sort_by(&:created_at),
          )
        end
        .sort_by(&:scanned_at)

    puts "took #{DATE_HELPER.distance_of_time_in_words_to_now(now, include_seconds: true)}"
    puts "parsing #{fav_groups.size} fav scan groups..."

    # the first group has no 'delta' as it's the first scan
    seen_fa_ids = T.let(Set.new, T::Set[Integer])
    if first_group = fav_groups.first
      seen_fa_ids.merge(fa_ids_in_group(first_group))
      hle = first_group.root_hle
      puts "initial group #{first_group.scanned_at.utc} - #{hle.id} - has #{seen_fa_ids.size} favs"
    end

    favs_scans_by_log_entry =
      @user.favs_scans.to_h { |scan| [T.must(scan.log_entry_id), scan] }

    num_created = 0
    fav_groups.each_cons(2) do |prev_group, fav_group|
      fav_group = T.must(fav_group)
      prev_group = T.must(prev_group)

      since_last_scan =
        ActiveSupport::Duration.build(
          fav_group.scanned_at - prev_group.scanned_at,
        )

      fa_ids_in_group = fa_ids_in_group(fav_group)

      new_ids = fa_ids_in_group - seen_fa_ids
      since_last_scan_str =
        "#{DATE_HELPER.distance_of_time_in_words(since_last_scan)}".ljust(
          20,
          ".",
        )
      print "#{fav_group.scanned_at.utc} - #{fav_group.hles.first&.id} - #{since_last_scan_str}.#{new_ids.size.to_s.rjust(4, ".")}"
      seen_fa_ids.merge(new_ids)

      unless favs_scans_by_log_entry[T.must(fav_group.root_hle.id)]
        print " (created favs scan)"
        @user.favs_scans.create!(
          log_entry: fav_group.root_hle,
          duration_since_last_scan: since_last_scan,
          num_added: new_ids.size,
          num_total: seen_fa_ids.size,
        )
        num_created += 1
      end

      puts
    end

    Stats.new(
      total_groups: fav_groups.size,
      total_hles: hles.size,
      total_favs: seen_fa_ids.size,
      total_created: num_created,
    )
  end

  sig { params(group: FavsGroup).returns(T::Set[Integer]) }
  def fa_ids_in_group(group)
    fa_ids = T.let(Set.new, T::Set[Integer])
    group.hles.each do |hle|
      page_parser =
        Domain::Fa::Parser::Page.from_log_entry(hle, require_logged_in: false)
      next [] unless page_parser.logged_in?
      next [] unless page_parser.probably_listings_page?
      fa_ids.merge(page_parser.submissions_parsed.map(&:id).compact)
    end
    fa_ids
  end
end
