# typed: false
require "rails_helper"

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

  let(:avatar_url_str) { "https://example.com/avatar.jpg" }
  let(:avatar_state) { "pending" }
  let(:existing_downloaded_at) { nil }
  let(:existing_log_entry_id) { nil }
  let(:user) { create(:domain_user_inkbunny_user) }
  let!(:avatar) do
    create(
      :domain_user_avatar,
      user: user,
      url_str: avatar_url_str,
      state: avatar_state,
      downloaded_at: existing_downloaded_at,
      last_log_entry_id: existing_log_entry_id,
      log_entry_id: existing_log_entry_id,
    )
  end

  def perform_job
    perform_now({ avatar: avatar })
  end

  context "when avatar_url_str is blank" do
    let(:avatar_url_str) { nil }

    it "logs a warning and returns" do
      perform_job
      avatar.reload
      expect(avatar.state).to eq("pending")
      expect(avatar.log_entry).to be_nil
    end
  end

  context "when avatar download succeeds" do
    let! :log_entries do
      HttpClientMockHelpers.init_http_client_mock(
        http_client_mock,
        [
          {
            uri: avatar_url_str,
            status_code: 200,
            content_type: "image/jpeg",
            contents: "test",
            caused_by_entry_idx: nil,
          },
        ],
      )
    end

    it "sets ok state and updates file info" do
      perform_job

      user.reload
      expect(avatar.state).to eq("ok")
      expect(avatar.downloaded_at).to be_within(1.second).of(
        log_entries[0].created_at,
      )
      expect(avatar.log_entry).to eq(log_entries[0])
    end

    context "when previous file exists" do
      let(:existing_log_entry) { create(:http_log_entry) }
      let(:existing_log_entry_id) { existing_log_entry.id }
      let(:existing_blob_entry) do
        create(:blob_entry, content: "previous", content_type: "image/jpeg")
      end
      let(:avatar_state) { :ok }
      let(:existing_downloaded_at) { 1.day.ago }

      it "updates file info" do
        perform_job

        user.reload
        expect(avatar.state).to eq("ok")
        expect(avatar.downloaded_at).to be_within(1.second).of(
          log_entries[0].created_at,
        )
        expect(avatar.log_entry).to eq(log_entries[0])
      end
    end
  end

  context "when avatar download returns 404" do
    let! :log_entries do
      HttpClientMockHelpers.init_http_client_mock(
        http_client_mock,
        [
          {
            uri: avatar_url_str,
            status_code: 404,
            content_type: "text/html",
            contents: "not found",
            caused_by_entry_idx: nil,
          },
        ],
      )
    end

    it "sets not_found state" do
      perform_job

      user.reload
      expect(avatar.state).to eq("file_404")
      expect(avatar.downloaded_at).to be_nil
      expect(avatar.last_log_entry).to eq(log_entries[0])
      expect(avatar.log_entry).to be_nil
    end

    context "when previous file exists" do
      let(:existing_log_entry) { create(:http_log_entry) }
      let(:existing_log_entry_id) { existing_log_entry.id }
      let(:existing_blob_entry) do
        create(:blob_entry, content: "previous", content_type: "image/jpeg")
      end
      let(:avatar_state) { :ok }
      let(:existing_downloaded_at) { 1.day.ago }

      it "keeps previous file but updates state to not_found" do
        perform_job

        user.reload
        expect(avatar.state).to eq("file_404")
        expect(avatar.downloaded_at).to be_within(1.second).of(
          existing_downloaded_at,
        )
        expect(avatar.last_log_entry).to eq(log_entries[0])
        expect(avatar.log_entry).to eq(existing_log_entry)
      end
    end
  end

  context "when avatar download fails and then succeeds" do
    let! :log_entries do
      HttpClientMockHelpers.init_http_client_mock(
        http_client_mock,
        [
          {
            uri: avatar_url_str,
            status_code: 500,
            content_type: "text/html",
            contents: "server error",
            caused_by_entry_idx: nil,
          },
          {
            uri: avatar_url_str,
            status_code: 200,
            content_type: "image/jpeg",
            contents: "test",
            caused_by_entry_idx: nil,
          },
        ],
      )
    end

    it "sets error state and then ok state" do
      perform_now({ avatar: avatar }, should_raise: Scraper::JobBase::JobError)
      user.reload
      avatar.reload

      expect(avatar.state).to eq("http_error")
      expect(avatar.last_log_entry).to eq(log_entries[0])
      expect(avatar.log_entry).to be_nil
      expect(avatar.error_message).to include("500")

      perform_now({ avatar: avatar })
      user.reload
      avatar.reload

      expect(avatar.state).to eq("ok")
      expect(avatar.error_message).to be_nil
      expect(avatar).to be_present
      expect(avatar.last_log_entry).to eq(log_entries[1])
      expect(avatar.log_entry).to eq(log_entries[1])
    end

    context "when previous file exists" do
      let(:existing_log_entry) { create(:http_log_entry) }
      let(:existing_log_entry_id) { existing_log_entry.id }
      let(:existing_blob_entry) do
        create(:blob_entry, content: "previous", content_type: "image/jpeg")
      end
      let(:avatar_state) { :ok }
      let(:existing_downloaded_at) { 1.day.ago }

      it "keeps previous file but updates state to error" do
        perform_now(
          { avatar: avatar },
          should_raise: Scraper::JobBase::JobError,
        )

        user.reload
        avatar.reload

        expect(avatar.state).to eq("http_error")
        expect(avatar.last_log_entry).to eq(log_entries[0])
        expect(avatar.log_entry).to eq(existing_log_entry)
        expect(avatar.error_message).to include("500")
        expect(avatar.downloaded_at).to be_within(1.second).of(
          existing_downloaded_at,
        )

        perform_now({ avatar: avatar })
        user.reload
        avatar.reload

        expect(avatar.state).to eq("ok")
        expect(avatar.error_message).to be_nil
        expect(avatar).to be_present
        expect(avatar.last_log_entry).to eq(log_entries[1])
        expect(avatar.log_entry).to eq(log_entries[1])
      end
    end
  end
end
