# typed: false
require "rails_helper"

describe Domain::Fa::Job::BrowsePageJob do
  let(:http_client_mock) { instance_double("::Scraper::HttpClient") }
  before { Scraper::ClientFactory.http_client_mock = http_client_mock }

  shared_context "user and post getters" do
    let(:find_creator) do
      proc { Domain::User::FaUser.find_by(url_name: "ruby69r") }
    end
    let(:find_post) { proc { Domain::Post::FaPost.find_by(fa_id: 51_509_268) } }
    before do
      expect(find_post.call).to be_nil
      expect(find_creator.call).to be_nil
    end
  end

  shared_context "create user and post" do
    before do
      creator =
        Domain::User::FaUser.create!({ name: "Ruby_69r", url_name: "ruby69r" })
      Domain::Post::FaPost.create!({ fa_id: 51_509_268, creator: creator })
    end
  end

  shared_examples "enqueue post scan" do |expect_to_enqueue|
    if expect_to_enqueue
      it "enqueues post scan job" do
        expect(
          SpecUtil.enqueued_job_args(Domain::Fa::Job::ScanPostJob),
        ).to match(
          [
            hash_including(
              post: find_post.call,
              caused_by_entry: log_entries[0],
            ),
          ],
        )
      end
    end

    unless expect_to_enqueue
      it "does not enqueue post scan job" do
        expect(SpecUtil.enqueued_jobs(Domain::Fa::Job::ScanPostJob)).to eq([])
      end
    end
  end

  shared_examples "enqueue file scan" do |expect_to_enqueue|
    if expect_to_enqueue
      it "enqueues file scan job" do
        expect(
          SpecUtil.enqueued_job_args(Domain::Fa::Job::ScanFileJob),
        ).to match(
          [
            hash_including(
              post_file: find_post.call.file,
              caused_by_entry: log_entries[0],
            ),
          ],
        )
      end
    else
      it "does not enqueue post scan job" do
        expect(SpecUtil.enqueued_jobs(Domain::Fa::Job::ScanFileJob)).to eq([])
      end
    end
  end

  shared_examples "enqueue user page scan" do |expect_to_enqueue|
    if expect_to_enqueue
      it "enqueues user page job" do
        expect(
          SpecUtil.enqueued_job_args(Domain::Fa::Job::UserPageJob),
        ).to match(
          [
            hash_including(
              user: find_creator.call,
              caused_by_entry: log_entries[0],
            ),
          ],
        )
      end
    end

    unless expect_to_enqueue
      it "does not enqueue user page job", quiet: false do
        expect(SpecUtil.enqueued_jobs(Domain::Fa::Job::UserPageJob)).to eq([])
      end
    end
  end

  it "enqueues one" do
    expect do
      ret = described_class.perform_later({})
      expect(ret).not_to be(Exception)
    end.to change(GoodJob::Job, :count).by(1)
  end

  it "does not enqueue more than one" do
    expect do
      described_class.perform_later({})
      described_class.perform_later({})
    end.to change(GoodJob::Job, :count).by(1)
  end

  it "can be enqueued in a bulk GoodJob batch" do
    expect do
      GoodJob::Bulk.enqueue do
        described_class.perform_later({})
        described_class.perform_later({})
      end
    end.to change(GoodJob::Job, :count).by(1)
  end

  context "with no posts found on page" do
    let! :log_entries do
      HttpClientMockHelpers.init_http_client_mock(
        http_client_mock,
        [
          {
            uri: "https://www.furaffinity.net/browse/",
            status_code: 200,
            content_type: "text/html",
            contents:
              SpecUtil.read_fixture_file(
                "domain/fa/browse/browse_page_no_submissions.html",
              ),
            caused_by_entry_idx: nil,
          },
        ],
      )
    end

    it "requests only one page" do
      perform_now({})
    end
  end

  context "duplicates found on the page" do
    let! :log_entries do
      HttpClientMockHelpers.init_http_client_mock(
        http_client_mock,
        [
          {
            uri: "https://www.furaffinity.net/browse/",
            status_code: 200,
            content_type: "text/html",
            contents:
              SpecUtil.read_fixture_file(
                "domain/fa/browse/browse_page_duplicates.html",
              ),
            caused_by_entry_idx: nil,
          },
          {
            uri: "https://www.furaffinity.net/browse/2/",
            status_code: 200,
            content_type: "text/html",
            contents:
              SpecUtil.read_fixture_file(
                "domain/fa/browse/browse_page_no_submissions.html",
              ),
            caused_by_entry_idx: 0,
          },
        ],
      )
    end

    it "succeeds" do
      expect { perform_now({}) }.not_to raise_error
    end
  end

  context "with one unseen post" do
    include_context "user and post getters"
    let! :log_entries do
      HttpClientMockHelpers.init_http_client_mock(
        http_client_mock,
        [
          {
            uri: "https://www.furaffinity.net/browse/",
            status_code: 200,
            content_type: "text/html",
            contents:
              SpecUtil.read_fixture_file(
                "domain/fa/browse/browse_page_one_submission.html",
              ),
            caused_by_entry_idx: nil,
          },
          {
            uri: "https://www.furaffinity.net/browse/2/",
            status_code: 200,
            content_type: "text/html",
            contents:
              SpecUtil.read_fixture_file(
                "domain/fa/browse/browse_page_no_submissions.html",
              ),
            caused_by_entry_idx: 0,
          },
        ],
      )
    end

    it "creates a new post" do
      expect do perform_now({}) end.to change(Domain::Post::FaPost, :count).by(
        1,
      )
    end

    it "creates a new user" do
      expect do perform_now({}) end.to change(Domain::User::FaUser, :count).by(
        1,
      )
    end

    it "creates a post with the right attributes" do
      perform_now({})
      expect(find_post.call.state).to eq("ok")
      expect(find_post.call.title).to eq("reminder YCH AUCTION")
      expect(find_post.call.creator).to eq(find_creator.call)

      expect(SpecUtil.enqueued_job_args).to match(
        [
          hash_including({ post: find_post.call }),
          hash_including({ user: find_creator.call }),
        ],
      )
    end

    it "creates a post when enqueueing found links" do
      perform_now({ skip_enqueue_found_links: false })
      expect(find_post.call.state).to eq("ok")
      expect(find_post.call.title).to eq("reminder YCH AUCTION")
      expect(find_post.call.creator).to eq(find_creator.call)
      expect(SpecUtil.enqueued_job_args(Domain::Fa::Job::UserPageJob)).to match(
        including(
          hash_including(
            { user: find_creator.call, caused_by_entry: log_entries[0] },
          ),
        ),
      )
    end

    it "creates a user with the right attributes" do
      perform_now({})
      expect(find_creator.call.name).to eq("Ruby_69r")
    end
  end

  context "with gaps between posts" do
    include_context "user and post getters"
    let! :log_entries do
      HttpClientMockHelpers.init_http_client_mock(
        http_client_mock,
        [
          {
            uri: "https://www.furaffinity.net/browse/",
            status_code: 200,
            content_type: "text/html",
            contents:
              SpecUtil.read_fixture_file(
                "domain/fa/browse/browse_page_two_submissions.html",
              ),
            caused_by_entry_idx: nil,
          },
          {
            uri: "https://www.furaffinity.net/browse/2/",
            status_code: 200,
            content_type: "text/html",
            contents:
              SpecUtil.read_fixture_file(
                "domain/fa/browse/browse_page_no_submissions.html",
              ),
            caused_by_entry_idx: 0,
          },
        ],
      )
    end

    it "enqueues scan post jobs" do
      perform_now({})

      post1 = Domain::Post::FaPost.find_by(fa_id: 51_509_268)
      post3 = Domain::Post::FaPost.find_by(fa_id: 51_509_266)

      expect(post1).to_not be_nil
      expect(post1.creator).to eq(find_creator.call)

      expect(post3).to_not be_nil
      expect(post3.creator).to eq(find_creator.call)

      expect(SpecUtil.enqueued_job_args(Domain::Fa::Job::ScanPostJob)).to match(
        [
          hash_including({ post: post1, caused_by_entry: log_entries[0] }),
          hash_including({ post: post3, caused_by_entry: log_entries[0] }),
        ],
      )
    end
  end

  context "with one seen post" do
    include_context "user and post getters"
    include_context "create user and post"
    let! :log_entries do
      HttpClientMockHelpers.init_http_client_mock(
        http_client_mock,
        [
          {
            uri: "https://www.furaffinity.net/browse/",
            status_code: 200,
            content_type: "text/html",
            contents:
              SpecUtil.read_fixture_file(
                "domain/fa/browse/browse_page_one_submission.html",
              ),
            caused_by_entry_idx: nil,
          },
        ],
      )
    end

    context "and nothing yet scanned" do
      before { perform_now({}) }
      include_examples "enqueue post scan", true
      include_examples "enqueue file scan", false
      include_examples "enqueue user page scan", true
    end

    context "and post page scanned" do
      before do
        post = find_post.call
        post.scanned_at = 1.hour.ago
        post.files.create!(url_str: "http://www.example.com/img.jpg")
        post.save!
        perform_now({})
      end

      include_examples "enqueue post scan", false
      include_examples "enqueue file scan", true
      include_examples "enqueue user page scan", true
    end

    context "and post file scanned" do
      before do
        post = find_post.call
        create(:domain_post_file, post:)
        post.scanned_at = 1.hour.ago
        post.save!
        perform_now({})
      end

      include_examples "enqueue post scan", false
      include_examples "enqueue file scan", false
      include_examples "enqueue user page scan", true
    end

    context "and post is marked as removed" do
      before do
        post = find_post.call
        post.state_removed!
        post.save!
        perform_now({})
      end

      it "marks the post as ok" do
        expect(find_post.call.state).to eq("ok")
      end

      include_examples "enqueue post scan", true
      include_examples "enqueue file scan", false
      include_examples "enqueue user page scan", true
    end

    context "and post scanned but file is a terminal error state" do
      before do
        post = find_post.call
        create(:domain_post_file, post:, state: "terminal_error")
        post.scanned_at = 1.hour.ago
        post.save!
        perform_now({})
      end

      include_examples "enqueue post scan", true
      include_examples "enqueue file scan", false
      include_examples "enqueue user page scan", true
    end

    context "and user gallery already scanned" do
      before do
        creator = find_creator.call
        creator.scanned_gallery_at = 1.hour.ago
        creator.save!
        perform_now({})
      end

      include_examples "enqueue post scan", true
      include_examples "enqueue file scan", false
      include_examples "enqueue user page scan", true
    end

    context "and user page already scanned" do
      before do
        creator = find_creator.call
        creator.scanned_page_at = 1.hour.ago
        creator.save!
        perform_now({})
      end

      # include_examples "enqueue post scan", true
      # include_examples "enqueue file scan", false
      include_examples "enqueue user page scan", false
    end
  end

  context "with a page that responds with an error" do
    let! :log_entries do
      HttpClientMockHelpers.init_http_client_mock(
        http_client_mock,
        [
          {
            uri: "https://www.furaffinity.net/browse/",
            status_code: 503,
            content_type: "text/html",
            contents:
              SpecUtil.read_fixture_file(
                "domain/fa/browse/browse_page_no_submissions.html",
              ),
          },
        ],
      )
    end

    it "fails with a JobError" do
      perform_now({}, should_raise: Scraper::JobBase::JobError)
    end

    it "has a useful error message" do
      perform_now({}, should_raise: /non 200 response for/)
    end
  end
end
