# frozen_string_literal: true

RSpec.describe UserAvatar do
  fab!(:user) { Fabricate(:user) }
  let(:avatar) { user.create_user_avatar! }

  describe "#update_gravatar!" do
    let(:temp) { Tempfile.new("test") }
    fab!(:upload) { Fabricate(:upload, user: user) }

    describe "when working" do
      before do
        temp.binmode
        # tiny valid png
        temp.write(
          Base64.decode64(
            "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==",
          ),
        )
        temp.rewind
        FileHelper.expects(:download).returns(temp)
      end

      after { temp.unlink }

      it "can update gravatars" do
        freeze_time Time.now

        expect { avatar.update_gravatar! }.to change { Upload.count }.by(1)
        expect(avatar.gravatar_upload).to eq(Upload.last)
        expect(avatar.last_gravatar_download_attempt).to eq(Time.now)
        expect(user.reload.uploaded_avatar).to eq(nil)

        expect do avatar.destroy end.to_not change { Upload.count }
      end

      it "updates gravatars even if uploads have been disabled" do
        SiteSetting.authorized_extensions = ""

        expect { avatar.update_gravatar! }.to change { Upload.count }.by(1)
        expect(avatar.gravatar_upload).to eq(Upload.last)
      end

      describe "when user has an existing custom upload" do
        it "should not change the user's uploaded avatar" do
          user.update!(uploaded_avatar: upload)

          avatar.update!(custom_upload: upload, gravatar_upload: Fabricate(:upload, user: user))

          avatar.update_gravatar!

          expect(upload.reload).to eq(upload)
          expect(user.reload.uploaded_avatar).to eq(upload)
          expect(avatar.reload.custom_upload).to eq(upload)
          expect(avatar.gravatar_upload).to eq(Upload.last)
        end
      end

      describe "when user has an existing gravatar" do
        it "should update the user's uploaded avatar correctly" do
          user.update!(uploaded_avatar: upload)
          avatar.update!(gravatar_upload: upload)

          avatar.update_gravatar!

          # old upload to be cleaned up via clean_up_uploads
          expect(Upload.find_by(id: upload.id)).not_to eq(nil)

          new_upload = Upload.last

          expect(user.reload.uploaded_avatar).to eq(new_upload)
          expect(avatar.reload.gravatar_upload).to eq(new_upload)
        end
      end
    end

    describe "when failing" do
      it "always update 'last_gravatar_download_attempt'" do
        freeze_time Time.now

        FileHelper.expects(:download).raises(SocketError)

        expect do expect { avatar.update_gravatar! }.to raise_error(SocketError) end.to_not change {
          Upload.count
        }

        expect(avatar.last_gravatar_download_attempt).to eq(Time.now)
      end
    end

    describe "404 should be silent, nothing to do really" do
      it "does nothing when avatar is 404" do
        SecureRandom.stubs(:urlsafe_base64).returns("5555")
        freeze_time Time.now

        stub_request(
          :get,
          "https://www.gravatar.com/avatar/#{avatar.user.email_hash}.png?d=404&reset_cache=5555&s=#{Discourse.avatar_sizes.max}",
        ).to_return(status: 404, body: "", headers: {})

        expect do avatar.update_gravatar! end.to_not change { Upload.count }

        expect(avatar.last_gravatar_download_attempt).to eq(Time.now)
      end
    end

    it "should not raise an error when there's no primary_email" do
      avatar.user.primary_email.destroy
      avatar.user.reload

      # If raises an error, test fails
      avatar.update_gravatar!
    end
  end

  describe ".import_url_for_user" do
    it "creates user_avatar record if missing" do
      user = Fabricate(:user)
      user.user_avatar.destroy
      user.reload

      FileHelper.stubs(:download).returns(file_from_fixtures("logo.png"))

      UserAvatar.import_url_for_user("logo.png", user)
      user.reload

      expect(user.uploaded_avatar_id).not_to eq(nil)
      expect(user.user_avatar.custom_upload_id).to eq(user.uploaded_avatar_id)
    end

    it "can leave gravatar alone" do
      upload = Fabricate(:upload)

      user = Fabricate(:user, uploaded_avatar_id: upload.id)
      user.user_avatar.update_columns(gravatar_upload_id: upload.id)

      stub_request(:get, "http://thisfakesomething.something.com/").to_return(
        status: 200,
        body: file_from_fixtures("logo.png"),
        headers: {
        },
      )

      url = "http://thisfakesomething.something.com/"

      expect do UserAvatar.import_url_for_user(url, user, override_gravatar: false) end.to change {
        Upload.count
      }.by(1)

      user.reload
      expect(user.uploaded_avatar_id).to eq(upload.id)

      last_id = Upload.last.id

      expect(last_id).not_to eq(upload.id)
      expect(user.user_avatar.custom_upload_id).to eq(last_id)
    end

    describe "when avatar url returns an invalid status code" do
      it "should not do anything" do
        stub_request(:get, "http://thisfakesomething.something.com/").to_return(
          status: 500,
          body: "",
          headers: {
          },
        )

        url = "http://thisfakesomething.something.com/"

        expect do UserAvatar.import_url_for_user(url, user) end.to_not change { Upload.count }

        user.reload

        expect(user.uploaded_avatar_id).to eq(nil)
        expect(user.user_avatar.custom_upload_id).to eq(nil)
      end
    end

    it "doesn't error out if the remote request fails" do
      FileHelper.stubs(:download).raises(FinalDestination::SSRFDetector::LookupFailedError.new)

      expect { UserAvatar.import_url_for_user(anything, user) }.not_to raise_error
    end
  end

  describe "ensure_consistency!" do
    it "cleans up incorrectly sized avatars" do
      SiteSetting.avatar_sizes = "10|20|30"

      upload = Fabricate(:upload)
      user_avatar = Fabricate(:user).user_avatar
      user_avatar.update_columns(custom_upload_id: upload.id)

      Fabricate(:optimized_image, upload: upload, width: 10, height: 10)
      Fabricate(:optimized_image, upload: upload, width: 15, height: 15)
      Fabricate(:optimized_image, upload: upload, width: 20, height: 20)

      UserAvatar.ensure_consistency!

      expect(OptimizedImage.where(upload_id: upload.id).pluck(:width, :height).sort).to eq(
        [[10, 10], [20, 20]],
      )

      # will not clean up if referenced
      Fabricate(:optimized_image, upload: upload, width: 15, height: 15)
      UploadReference.create!(upload: upload, target: Fabricate(:post))

      UserAvatar.ensure_consistency!

      expect(OptimizedImage.where(upload_id: upload.id).pluck(:width, :height).sort).to eq(
        [[10, 10], [15, 15], [20, 20]],
      )
    end

    it "will clean up dangling avatars" do
      upload1 = Fabricate(:upload)
      upload2 = Fabricate(:upload)

      user_avatar = Fabricate(:user).user_avatar
      user_avatar.update_columns(gravatar_upload_id: upload1.id, custom_upload_id: upload2.id)

      upload1.destroy!
      upload2.destroy!

      user_avatar.reload
      expect(user_avatar.gravatar_upload_id).to eq(nil)
      expect(user_avatar.custom_upload_id).to eq(nil)

      user_avatar.update_columns(gravatar_upload_id: upload1.id, custom_upload_id: upload2.id)

      UserAvatar.ensure_consistency!

      user_avatar.reload
      expect(user_avatar.gravatar_upload_id).to eq(nil)
      expect(user_avatar.custom_upload_id).to eq(nil)
    end
  end
end