# typed: false
require "rails_helper"

module FileJobSpec
  AN_IMAGE_SHA256 =
    "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
  AN_IMAGE_MD5 = "d41d8cd98f00b204e9800998ecf8427e"
  AN_IMAGE_PATH = "domain/e621/job/an-image.png"
  AN_IMAGE_URL = "https://inkbunny.net/file/foo.png"
end

describe Domain::Inkbunny::Job::StaticFileJob do
  let(:http_client_mock) { instance_double("::Scraper::HttpClient") }
  before { Scraper::ClientFactory.http_client_mock = http_client_mock }
  let(:file) do
    Domain::PostFile::InkbunnyPostFile.create!(
      {
        ib_id: 12_345,
        md5_initial: FileJobSpec::AN_IMAGE_MD5,
        md5_full: FileJobSpec::AN_IMAGE_MD5,
        url_str: FileJobSpec::AN_IMAGE_URL,
        file_name: "foo.png",
        ib_created_at: Time.now,
        file_order: 1,
        md5s: {
          initial_file_md5: FileJobSpec::AN_IMAGE_MD5,
        },
        post:
          Domain::Post::InkbunnyPost.create!(
            ib_id: 67_891,
            creator:
              Domain::User::InkbunnyUser.create!(
                ib_id: 12_345,
                name: "TheUser",
              ),
          ),
      },
    )
  end

  describe "#perform" do
    it "downloads the file if url_str is present" do
      caused_by_entry = create(:http_log_entry)
      log_entries =
        HttpClientMockHelpers.init_http_client_mock(
          http_client_mock,
          [
            {
              uri: FileJobSpec::AN_IMAGE_URL,
              status_code: 200,
              content_type: "image/png",
              contents:
                File.binread(
                  Rails
                    .root
                    .join(
                      "test/fixtures/files/images/thumb-036aaab6-low-quality.jpeg",
                    )
                    .to_s,
                ),
              caused_by_entry:,
            },
          ],
        )
      perform_now({ file:, caused_by_entry: })

      file.reload
      expect(file.log_entry).to eq(log_entries[0])
      expect(file.state).to eq("ok")
    end

    it "marks the post as errored if the download fails" do
      log_entries =
        HttpClientMockHelpers.init_http_client_mock(
          http_client_mock,
          [
            {
              uri: FileJobSpec::AN_IMAGE_URL,
              status_code: 404,
              content_type: "text/html",
              contents: "not found",
            },
          ],
        )

      perform_now({ file: file })

      file.reload
      expect(file.state).to eq("terminal_error")
      expect(file.retry_count).to eq(0)
      expect(file.last_status_code).to eq(404)
      expect(file.log_entry).to eq(log_entries[0])
    end

    it "recovers from a failed download" do
      log_entries =
        HttpClientMockHelpers.init_http_client_mock(
          http_client_mock,
          [
            {
              uri: FileJobSpec::AN_IMAGE_URL,
              status_code: 500,
              content_type: "text/html",
              contents: "not found",
            },
            {
              uri: FileJobSpec::AN_IMAGE_URL,
              status_code: 200,
              content_type: "image/png",
              contents: SpecUtil.read_fixture_file(FileJobSpec::AN_IMAGE_PATH),
            },
          ],
        )

      perform_now({ file: file }, should_raise: true)
      file.reload
      expect(file.log_entry).to eq(log_entries[0])
      expect(file.state).to eq("retryable_error")
      expect(file.retry_count).to eq(1)
      expect(file.last_status_code).to eq(500)

      perform_now({ file: file })
      file.reload
      expect(file.log_entry).to eq(log_entries[1])
      expect(file.state).to eq("ok")
      expect(file.retry_count).to eq(1)
      expect(file.last_status_code).to eq(200)
    end

    it "throws on a non-404 error in order to retry later" do
      log_entries =
        HttpClientMockHelpers.init_http_client_mock(
          http_client_mock,
          [
            {
              uri: FileJobSpec::AN_IMAGE_URL,
              status_code: 500,
              content_type: "text/html",
              contents: "not found",
            },
          ] * 3,
        )

      3.times.map do |retry_num|
        perform_now({ file: file }, should_raise: true)
        file.reload
        expect(file.state).to eq("retryable_error")
        expect(file.retry_count).to eq(retry_num + 1)
        expect(file.last_status_code).to eq(500)
        expect(file.log_entry).to eq(log_entries[retry_num])
      end

      # the last retry should not throw, but simply bail out early
      perform_now({ file: file })
      file.reload
      expect(file.state).to eq("terminal_error")
      expect(file.log_entry).to eq(log_entries[2])
    end

    it "fails if file argument is missing" do
      expect { perform_now({}) }.to raise_error(/Expected type/)
    end
  end
end
