# typed: false
require "rails_helper"

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

  context "when updating multiple posts" do
    let(:api_submissions_url) do
      "https://inkbunny.net/api_submissions.php?submission_ids=3104202,3104200,3104197&show_description=yes&show_writing=yes&show_pools=yes"
    end

    let! :log_entries do
      HttpClientMockHelpers.init_http_client_mock(
        http_client_mock,
        [
          {
            method: :get,
            uri: api_submissions_url,
            content_type: "application/json",
            contents:
              SpecUtil.read_fixture_file(
                "domain/inkbunny/job/api_submissions.json",
              ),
          },
        ],
      )
    end

    let!(:user_thendyart) do
      Domain::User::InkbunnyUser.create!(ib_id: 941_565, name: "ThendyArt")
    end

    let!(:user_seff) do
      Domain::User::InkbunnyUser.create!(ib_id: 229_331, name: "Seff")
    end

    let!(:user_soulcentinel) do
      Domain::User::InkbunnyUser.create!(ib_id: 349_747, name: "SoulCentinel")
    end

    let!(:post_3104202) do
      Domain::Post::InkbunnyPost.create!(
        ib_id: 3_104_202,
        creator: user_thendyart,
        title: "Phantom Touch - Page 25",
        posted_at: Time.parse("2023-08-27 21:31:40.365597+02"),
        last_file_updated_at: Time.parse("2023-08-27 21:30:06.222262+02"),
        num_files: 1,
        rating: "adult",
        submission_type: "comic",
      )
    end

    let!(:post_3104200) do
      Domain::Post::InkbunnyPost.create!(
        ib_id: 3_104_200,
        creator: user_seff,
        title: "Camp Pines Sketch Dump (Aug 2023)",
        posted_at: Time.parse("2023-08-27 21:30:59.308046+02"),
        last_file_updated_at: Time.parse("2023-08-27 21:26:14.049+02"),
        num_files: 4,
        rating: "adult",
        submission_type: "picture_pinup",
      )
    end

    let!(:post_3104197) do
      Domain::Post::InkbunnyPost.create!(
        ib_id: 3_104_197,
        creator: user_soulcentinel,
        title: "Comm - BJ bird",
        posted_at: Time.parse("2023-08-27 21:29:37.995264+02"),
        last_file_updated_at: Time.parse("2023-08-27 21:24:23.653306+02"),
        num_files: 1,
        rating: "adult",
        submission_type: "picture_pinup",
      )
    end

    let(:ib_post_ids) { [3_104_202, 3_104_200, 3_104_197] }
    let(:pool_ib_post_ids) { [3_082_162, 3_095_240, 3_101_532, 3_098_688] }

    it "enqueues update jobs for missing posts" do
      perform_now({ ib_post_ids: ib_post_ids, caused_by_entry: nil })
      expect(
        SpecUtil.enqueued_jobs(Domain::Inkbunny::Job::UpdatePostsJob).length,
      ).to eq(1)

      expect(
        SpecUtil.enqueued_job_args(Domain::Inkbunny::Job::UpdatePostsJob),
      ).to match(
        [
          hash_including(
            ib_post_ids: pool_ib_post_ids,
            caused_by_entry: log_entries[0],
          ),
        ],
      )
    end

    it "enqueues update jobs for missing pools" do
      perform_now({ ib_post_ids: ib_post_ids, caused_by_entry: nil })
      expect(
        SpecUtil.enqueued_jobs(Domain::Inkbunny::Job::UpdatePoolJob).length,
      ).to eq(4)
      expect(
        SpecUtil.enqueued_job_args(Domain::Inkbunny::Job::UpdatePoolJob),
      ).to match(
        array_including(
          hash_including(
            pool: Domain::PostGroup::InkbunnyPool.find_by(ib_id: 71_061),
            caused_by_entry: log_entries[0],
          ),
          hash_including(
            pool: Domain::PostGroup::InkbunnyPool.find_by(ib_id: 35_628),
            caused_by_entry: log_entries[0],
          ),
          hash_including(
            pool: Domain::PostGroup::InkbunnyPool.find_by(ib_id: 35_045),
            caused_by_entry: log_entries[0],
          ),
          hash_including(
            pool: Domain::PostGroup::InkbunnyPool.find_by(ib_id: 83_746),
            caused_by_entry: log_entries[0],
          ),
        ),
      )
    end

    it "enqueues file jobs when file first gets an md5_initial" do
      file =
        post_3104202.files.create(
          ib_id: 4_652_537,
          state: "terminal_error",
          md5_initial: "",
        )
      file.save!

      perform_now({ ib_post_ids: ib_post_ids, caused_by_entry: nil })

      file.reload
      expect(file.md5_initial).to eq("fbeb553c483a346108beeada93d90086")
      expect(file.state).to eq("ok")
      job_args =
        SpecUtil.enqueued_job_args(Domain::Inkbunny::Job::StaticFileJob)
      expect(job_args).to include(
        hash_including(file:, caused_by_entry: log_entries[0]),
      )
    end

    it "throws an error when the md5_initial changes" do
      file =
        post_3104202.files.create!(
          ib_id: 4_652_537,
          state: "ok",
          md5_initial: "12345",
        )

      perform_now(
        { ib_post_ids: ib_post_ids, caused_by_entry: nil },
        should_raise: /md5_initial changed/,
      )

      expect(file.reload.md5_initial).to eq("12345")
    end

    it "updates posts with detailed information" do
      perform_now({ ib_post_ids: ib_post_ids, caused_by_entry: nil })

      # Check post details were updated
      post_3104202.reload
      expect(post_3104202.description).to match(/Vulk grabs Lyra/)
      expect(post_3104202.writing).to eq("")
      expect(post_3104202.num_views).to eq(202)
      expect(post_3104202.deep_updated_at).to be_within(3.seconds).of(Time.now)

      post_3104200.reload
      expect(post_3104200.description).to match(/Some requests from my Patreon/)
      expect(post_3104200.writing).to eq("")
      expect(post_3104200.num_views).to eq(690)
      expect(post_3104200.num_favs).to eq(139)
      expect(post_3104200.num_comments).to eq(2)
      expect(post_3104200.keywords).to match(
        array_including(
          including("keyword_id" => "128", "keyword_name" => "husky"),
          including("keyword_id" => "32715", "keyword_name" => "direwolf"),
        ),
      )
      expect(post_3104200.deep_update_log_entry).to eq(log_entries[0])
      expect(post_3104200.deep_updated_at).to be_within(3.seconds).of(Time.now)

      # Check user details were updated
      user_seff.reload
      expect(user_seff.avatar.url_str).to eq(
        "https://us.ib.metapix.net/usericons/large/176/176443_Seff_seffpfp.png",
      )

      # Check files were created
      expect(post_3104200.files.count).to eq(4)
      file_4652528 = post_3104200.files.find_by!(ib_id: 4_652_528)
      expect(file_4652528.file_order).to eq(0)
      expect(file_4652528.file_name).to eq("4652528_Seff_aug23sketches7-1.png")
      expect(file_4652528.url_str).to eq(
        "https://us.ib.metapix.net/files/full/4652/4652528_Seff_aug23sketches7-1.png",
      )
      expect(file_4652528.md5_initial).to eq("e1cf8388a8aa03e4f33dc442e8984e5b")
      expect(file_4652528.md5_full).to eq("07946e8d485664704b316cb218805367")

      # Check file jobs were enqueued
      file_jobs = SpecUtil.enqueued_jobs(Domain::Inkbunny::Job::StaticFileJob)
      expect(file_jobs.length).to eq(6)
      expect(file_jobs.map { |job| job[:args][0][:file].ib_id }.sort).to eq(
        [4_652_528, 4_652_530, 4_652_531, 4_652_534, 4_652_535, 4_652_537],
      )
      file_jobs.each do |job|
        expect(job[:args][0][:caused_by_entry]).to eq(log_entries[0])
      end
    end

    it "enqueues avatar jobs" do
      perform_now({ ib_post_ids: ib_post_ids, caused_by_entry: nil })
      user_soulcentinel.reload
      user_seff.reload
      user_thendyart.reload
      expect(user_soulcentinel.avatar.url_str).to eq(
        "https://us.ib.metapix.net/usericons/large/208/208598_SoulCentinel_fireb.gif",
      )

      expect(
        SpecUtil.enqueued_jobs(Domain::Inkbunny::Job::UserAvatarJob).length,
      ).to eq(3)

      expect(
        SpecUtil.enqueued_job_args(Domain::Inkbunny::Job::UserAvatarJob),
      ).to match(
        [
          { avatar: user_soulcentinel.avatar, caused_by_entry: log_entries[0] },
          { avatar: user_seff.avatar, caused_by_entry: log_entries[0] },
          { avatar: user_thendyart.avatar, caused_by_entry: log_entries[0] },
        ],
      )
    end

    it "associates posts with the correct pools" do
      perform_now({ ib_post_ids: ib_post_ids, caused_by_entry: nil })

      # Post 3104202 should be in "Phantom Touch" pool
      post_3104202.reload
      expect(post_3104202.post_group_joins.count).to eq(1)
      phantom_touch_pool = post_3104202.pools.first
      expect(phantom_touch_pool.ib_id).to eq(83_746)
      expect(phantom_touch_pool.name).to eq("Phantom Touch | Ongoing")
      expect(phantom_touch_pool.description).to eq(
        "18+ M/F Adult Comic\n\n(New Page Every Sunday)",
      )
      expect(phantom_touch_pool.count).to eq(26)

      # Post 3104197 should be in 3 pools
      post_3104197.reload
      expect(post_3104197.post_group_joins.count).to eq(3)
      pool_ids = post_3104197.pools.pluck(:ib_id).sort
      expect(pool_ids).to eq([35_045, 35_628, 71_061])
      animation_pool = post_3104197.pools.find_by(ib_id: 71_061)
      expect(animation_pool.name).to eq("Animation")
      expect(animation_pool.description).to eq("It moves!")

      # Post 3104200 should have no pools
      post_3104200.reload
      expect(post_3104200.post_group_joins.count).to eq(0)
      expect(post_3104200.pools).to be_empty
    end

    it "sets the owner of the pool to the creator of the post" do
      perform_now({ ib_post_ids: ib_post_ids })
      phantom_touch_pool =
        Domain::PostGroup::InkbunnyPool.find_by(ib_id: 83_746)
      expect(phantom_touch_pool.owner).to eq(user_thendyart)
    end

    it "sets correct left/right post IDs in pool joins" do
      perform_now({ ib_post_ids: ib_post_ids, caused_by_entry: nil })

      # Check post 3104197's pool joins
      post_3104197.reload
      animation_pool_join =
        post_3104197.post_group_joins.find_by(
          group: Domain::PostGroup::InkbunnyPool.find_by!(ib_id: 71_061),
        )
      expect(animation_pool_join.left_post.ib_id).to eq(3_082_162)
      expect(animation_pool_join.right_post).to be_nil

      # Check post 3104202's pool joins
      post_3104202.reload
      phantom_touch_pool_join = post_3104202.post_group_joins.first
      expect(phantom_touch_pool_join.left_post.ib_id).to eq(3_098_688)
      expect(phantom_touch_pool_join.right_post).to be_nil
    end

    it "enqueues update jobs for missing left/right posts" do
      perform_now({ ib_post_ids: ib_post_ids, caused_by_entry: nil })

      # Should enqueue jobs for posts 3082162 and 3095240
      update_jobs =
        SpecUtil.enqueued_jobs(Domain::Inkbunny::Job::UpdatePostsJob)
      update_job_post_ids =
        update_jobs.map { |job| job[:args][0][:ib_post_ids] }.flatten.sort
      expect(update_job_post_ids).to match_array(pool_ib_post_ids)

      update_jobs.each do |job|
        expect(job[:args][0][:caused_by_entry]).to eq(log_entries[0])
      end
    end
  end

  context "when a post's files change" do
    let(:api_submissions_url) do
      "https://inkbunny.net/api_submissions.php?submission_ids=1047334&show_description=yes&show_writing=yes&show_pools=yes"
    end

    let! :log_entries do
      HttpClientMockHelpers.init_http_client_mock(
        http_client_mock,
        [
          {
            method: :get,
            uri: api_submissions_url,
            content_type: "application/json",
            contents:
              SpecUtil.read_fixture_file(
                "domain/inkbunny/job/api_submissions_1047334_before.json",
              ),
          },
          {
            method: :get,
            uri: api_submissions_url,
            content_type: "application/json",
            contents:
              SpecUtil.read_fixture_file(
                "domain/inkbunny/job/api_submissions_1047334_after.json",
              ),
          },
        ],
      )
    end

    let!(:user_zzreg) do
      Domain::User::InkbunnyUser.create!(ib_id: 110_036, name: "zzreg")
    end

    let!(:post_1047334) do
      Domain::Post::InkbunnyPost.create!(
        ib_id: 1_047_334,
        creator: user_zzreg,
        title: "New Submission",
        posted_at: Time.parse("2016-03-13 22:18:52.32319+01"),
        last_file_updated_at: Time.parse("2016-03-13 22:18:52.32319+01"),
        num_files: 1,
        rating: "general",
        submission_type: "picture_pinup",
      )
    end

    it "updates post with new file information" do
      # First update - initial state
      perform_now({ ib_post_ids: [1_047_334], caused_by_entry: nil })

      post_1047334.reload
      expect(post_1047334.files.count).to eq(1)
      file_1445274 = post_1047334.files.find_by!(ib_id: 1_445_274)
      expect(file_1445274.md5_initial).to eq("0127e88651e73140718f3b8f7f2037d5")
      expect(file_1445274.md5_full).to eq("aa0e22f86a9c345ead2bd711a1c91986")
      expect(file_1445274.file_name).to eq(
        "1445274_zzreg_sname-yellow-small-border.png",
      )

      # Second update - file has changed
      perform_now({ ib_post_ids: [1_047_334], caused_by_entry: nil })

      post_1047334.reload
      expect(post_1047334.files.count).to eq(2)
      expect(post_1047334.last_file_updated_at).to be_within(3.seconds).of(
        Time.parse("2023-09-14 19:07:45.735562+02"),
      )

      # Old file should still exist
      expect(file_1445274.reload.attributes).to include(
        "md5_initial" => "0127e88651e73140718f3b8f7f2037d5",
        "md5_full" => "aa0e22f86a9c345ead2bd711a1c91986",
        "file_name" => "1445274_zzreg_sname-yellow-small-border.png",
      )

      # New file should be created
      file_4680214 = post_1047334.files.find_by!(ib_id: 4_680_214)
      expect(file_4680214.attributes).to include(
        "md5_initial" => "9fbfbdf3cc6d8b3538b7edbfe36bde8c",
        "md5_full" => "d2e30d953f4785e22c3d9c722249a974",
        "file_name" =>
          "4680214_zzreg_how-to-photograph-snakes-15-1200x900-cropped.jpg",
      )

      # Check file jobs were enqueued for both updates
      file_jobs = SpecUtil.enqueued_jobs(Domain::Inkbunny::Job::StaticFileJob)
      expect(file_jobs.length).to eq(2)
      expect(file_jobs.map { |job| job[:args][0][:file].ib_id }.sort).to eq(
        [1_445_274, 4_680_214],
      )
      expect(file_jobs[0][:args][0][:caused_by_entry]).to eq(log_entries[0])
      expect(file_jobs[1][:args][0][:caused_by_entry]).to eq(log_entries[1])
    end
  end

  context "when a submission has files with null MD5 sums" do
    let(:api_submissions_url) do
      "https://inkbunny.net/api_submissions.php?submission_ids=2637105&show_description=yes&show_writing=yes&show_pools=yes"
    end

    let! :log_entries do
      HttpClientMockHelpers.init_http_client_mock(
        http_client_mock,
        [
          {
            method: :get,
            uri: api_submissions_url,
            content_type: "application/json",
            contents:
              SpecUtil.read_fixture_file(
                "domain/inkbunny/job/api_submissions_null_md5sum.json",
              ),
          },
        ],
      )
    end

    let!(:user_friar) do
      create(:domain_user_inkbunny_user, ib_id: 1664, name: "Friar")
    end

    let!(:post_2637105) do
      create(
        :domain_post_inkbunny_post,
        ib_id: 2_637_105,
        creator: user_friar,
        num_files: 5,
      )
    end

    it "handles files with null MD5 sums correctly" do
      perform_now({ ib_post_ids: [2_637_105], caused_by_entry: nil })

      post_2637105.reload
      expect(post_2637105.description).to eq(
        "Berry and Friar cheer up Barrett.",
      )
      expect(post_2637105.writing).to eq("")
      expect(post_2637105.num_views).to eq(170)
      expect(post_2637105.num_favs).to eq(11)
      expect(post_2637105.num_comments).to eq(1)
      expect(post_2637105.deep_update_log_entry).to eq(log_entries[0])
      expect(post_2637105.deep_updated_at).to be_within(3.seconds).of(Time.now)

      # Check that files were created, even those with null MD5 sums
      expect(post_2637105.files.count).to eq(5)

      file_3897070 = post_2637105.files.find_by!(ib_id: 3_897_070)
      expect(file_3897070.url_str).to eq(
        "https://tx.ib.metapix.net/files/full/3897/3897070_Friar_ffrbb4.png",
      )
      expect(file_3897070.attributes).to include(
        "md5_initial" => "",
        "md5_full" => "",
      )
      expect(file_3897070.state).to eq("terminal_error")

      # Check file jobs were enqueued for valid files
      file_jobs = SpecUtil.enqueued_jobs(Domain::Inkbunny::Job::StaticFileJob)
      expect(file_jobs.length).to eq(4)
      expect(file_jobs.map { |job| job[:args][0][:file].ib_id }.sort).to eq(
        [3_897_058, 3_897_061, 3_897_065, 3_897_067],
      )
      file_jobs.each do |job|
        expect(job[:args][0][:caused_by_entry]).to eq(log_entries[0])
      end
    end
  end
end
