# typed: false
require "rails_helper"

describe Domain::Fa::Job::UserFollowsJob do
  FOLLOWS_ON_ZZREG_PAGE = 10

  let(:http_client_mock) { instance_double("::Scraper::HttpClient") }
  let(:set_zzreg_http_mock) do
    proc do
      HttpClientMockHelpers.init_http_client_mock(
        http_client_mock,
        [
          {
            uri: "https://www.furaffinity.net/watchlist/by/zzreg/",
            status_code: 200,
            content_type: "text/html",
            contents:
              SpecUtil.read_fixture_file(
                "domain/fa/parser/redux/watchlist_zzreg.html",
              ),
            caused_by_entry_idx: nil,
          },
          {
            uri: "https://www.furaffinity.net/watchlist/by/zzreg/2/?",
            status_code: 200,
            content_type: "text/html",
            contents:
              SpecUtil.read_fixture_file(
                "domain/fa/parser/redux/watchlist_zzreg.html",
              ),
            caused_by_entry_idx: 0,
          },
        ],
      )
    end
  end

  before do
    Scraper::ClientFactory.http_client_mock = http_client_mock
    @zzreg_mock_log_entries = set_zzreg_http_mock.call
  end

  shared_context "zzreg user exists" do
    let!(:user) do
      Domain::User::FaUser.create!(name: "Zzreg", url_name: "zzreg")
    end
  end

  shared_examples "zzreg follow creation" do
    it "creates the right follows" do
      perform_now({ user: user })
      user.reload
      expect(user.followed_users.length).to eq(FOLLOWS_ON_ZZREG_PAGE)
      expect(user.scanned_follows_at).to_not be_nil
    end
  end

  context "performed with a user that doesn't exist yet" do
    it "creates the scanned user and followed users" do
      expect do perform_now({ url_name: "zzreg" }) end.to change(
        Domain::User::FaUser,
        :count,
      ).by(FOLLOWS_ON_ZZREG_PAGE + 1)
    end

    it "enqueues a user page job" do
      perform_now({ url_name: "zzreg" })
      zzreg = Domain::User::FaUser.find_by!(url_name: "zzreg")
      expect(zzreg).to_not be_nil
      expect(
        SpecUtil
          .enqueued_jobs(Domain::Fa::Job::UserPageJob)
          .find { |job| job[:args][0][:user] == zzreg },
      ).to_not be_nil
    end
  end

  context "when the scanned user already exists" do
    include_context "zzreg user exists"

    it "can be performed by url_name" do
      expect do perform_now({ url_name: "zzreg" }) end.to change(
        Domain::User::FaUser,
        :count,
      ).by(FOLLOWS_ON_ZZREG_PAGE)
    end

    it "can be performed by direct post object" do
      expect do perform_now({ user: user }) end.to change(
        Domain::User::FaUser,
        :count,
      ).by(FOLLOWS_ON_ZZREG_PAGE)
    end

    it "does not enqueue a user page job" do
      perform_now({ user: user })
      expect(
        SpecUtil
          .enqueued_jobs(Domain::Fa::Job::UserPageJob)
          .find { |job| job[:args][0][:user] == user },
      ).to be_nil
    end

    it "can be ran twice to no ill effect" do
      perform_now({ user: user })
      set_zzreg_http_mock.call
      perform_now({ user: user, force_scan: true })
    end

    include_examples "zzreg follow creation"
  end

  context "some scanned users already exist" do
    include_context "zzreg user exists"

    let!(:followed) do
      Domain::User::FaUser.create!(
        # name in html is Agi_Type01, it's intentionally changed here
        name: "AGI_Type01",
        url_name: "agitype01",
        num_submissions: 10,
      )
    end

    it "does not ovewrite the existing user" do
      original_updated_at = followed.updated_at

      expect do perform_now({ user: user }) end.to change(
        Domain::User::FaUser,
        :count,
      ).by(FOLLOWS_ON_ZZREG_PAGE - 1)

      followed.reload
      expect(followed.num_submissions).to eq(10)
      expect(followed.created_at).to eq(original_updated_at)
      # the upsert should only create new user models, not modify
      # existing ones, even for casing changes
      expect(followed.name).to eq("AGI_Type01")

      # newly created users should have the right 'first_seen_entry' id
      accelo_user = Domain::User::FaUser.find_by!(url_name: "accelo")
      expect(accelo_user).to_not be_nil
    end

    it "newly inserted users have a name associated with them" do
      perform_now({ user: user })

      user = Domain::User::FaUser.find_by!(url_name: "accelo")
      expect(user).to_not be_nil
      expect(user.name).to eq("Accelo")
    end

    include_examples "zzreg follow creation"
  end

  context "the set of followed users changes" do
    include_context "zzreg user exists"

    # this is a user that is not in the watchlist
    let!(:smaz_user) do
      Domain::User::FaUser.create!(name: "Smaz", url_name: "smaz")
    end
    # this is a user already in the watchlist
    let!(:agi_type01_user) do
      Domain::User::FaUser.create!(
        name: "Agi_Type01",
        url_name: "agitype01",
        num_submissions: 10,
      )
    end

    it "correctly adds and removes follows" do
      Domain::UserUserFollow.create!(from: user, to: smaz_user)
      Domain::UserUserFollow.create!(from: user, to: agi_type01_user)
      expect(user.followed_users.length).to eq(2)

      perform_now({ user: user })

      user.reload
      expect(user.followed_users.length).to eq(11)
      expect(user.followed_users).to include(smaz_user)
      expect(user.followed_users).to include(agi_type01_user)

      # correct user page jobs should be enqueued
      expect(
        SpecUtil
          .enqueued_job_args(Domain::Fa::Job::UserPageJob)
          .map { |args| args[:user] },
      ).not_to include(smaz_user)

      expect(
        SpecUtil
          .enqueued_job_args(Domain::Fa::Job::UserPageJob)
          .map { |args| args[:user] },
      ).not_to include(agi_type01_user)

      accelo = Domain::User::FaUser.find_by!(url_name: "accelo")
      expect(
        SpecUtil
          .enqueued_job_args(Domain::Fa::Job::UserPageJob)
          .map { |args| args[:user] },
      ).to include(accelo)
    end

    it "does not enqueue a job if the user is not new" do
      perform_now({ user: user })
      expect(SpecUtil.enqueued_jobs(Domain::Fa::Job::UserPageJob).length).to eq(
        FOLLOWS_ON_ZZREG_PAGE - 1,
      )
    end
  end
end
