# frozen_string_literal: true

RSpec.describe UserSerializer do
  fab!(:user) { Fabricate(:user, trust_level: 0) }

  context "with a TL0 user seen as anonymous" do
    let(:serializer) { UserSerializer.new(user, scope: Guardian.new, root: false) }
    let(:json) { serializer.as_json }
    let(:untrusted_attributes) do
      %i[
        bio_raw
        bio_cooked
        bio_excerpt
        location
        website
        website_name
        profile_background
        card_background
      ]
    end

    it "doesn't serialize untrusted attributes" do
      untrusted_attributes.each { |attr| expect(json).not_to have_key(attr) }
    end

    it "serializes correctly" do
      expect(json[:group_users]).to eq(nil)
      expect(json[:second_factor_enabled]).to eq(nil)
    end
  end

  context "as moderator" do
    it "serializes correctly" do
      json =
        UserSerializer.new(user, scope: Guardian.new(Fabricate(:moderator)), root: false).as_json

      expect(json[:group_users]).to eq(nil)
      expect(json[:second_factor_enabled]).to eq(nil)
    end
  end

  context "as current user" do
    it "serializes options correctly" do
      # so we serialize more stuff
      SiteSetting.default_other_auto_track_topics_after_msecs = 0
      SiteSetting.default_other_notification_level_when_replying = 3
      SiteSetting.default_other_new_topic_duration_minutes = 60 * 24

      user = Fabricate(:user)
      user.user_option.update(dynamic_favicon: true, skip_new_user_tips: true)

      json = UserSerializer.new(user, scope: Guardian.new(user), root: false).as_json

      expect(json[:user_option][:dynamic_favicon]).to eq(true)
      expect(json[:user_option][:skip_new_user_tips]).to eq(true)
      expect(json[:user_option][:new_topic_duration_minutes]).to eq(60 * 24)
      expect(json[:user_option][:auto_track_topics_after_msecs]).to eq(0)
      expect(json[:user_option][:notification_level_when_replying]).to eq(3)
      expect(json[:group_users]).to eq([])
      expect(json[:second_factor_enabled]).to eq(false)
    end
  end

  context "with a user" do
    let(:admin_user) { Fabricate(:admin) }
    let(:scope) { Guardian.new }
    fab!(:user)
    let(:serializer) { UserSerializer.new(user, scope: scope, root: false) }
    let(:json) { serializer.as_json }
    fab!(:upload)
    fab!(:upload2) { Fabricate(:upload) }

    context "when the scope user is admin" do
      let(:scope) { Guardian.new(admin_user) }

      it "returns the user's category notification levels, not the scope user's" do
        category1 = Fabricate(:category)
        category2 = Fabricate(:category)
        category3 = Fabricate(:category)
        category4 = Fabricate(:category)
        CategoryUser.create(
          category: category1,
          user: user,
          notification_level: CategoryUser.notification_levels[:muted],
        )
        CategoryUser.create(
          category: Fabricate(:category),
          user: admin_user,
          notification_level: CategoryUser.notification_levels[:muted],
        )
        CategoryUser.create(
          category: category2,
          user: user,
          notification_level: CategoryUser.notification_levels[:tracking],
        )
        CategoryUser.create(
          category: Fabricate(:category),
          user: admin_user,
          notification_level: CategoryUser.notification_levels[:tracking],
        )
        CategoryUser.create(
          category: category3,
          user: user,
          notification_level: CategoryUser.notification_levels[:watching],
        )
        CategoryUser.create(
          category: Fabricate(:category),
          user: admin_user,
          notification_level: CategoryUser.notification_levels[:watching],
        )
        CategoryUser.create(
          category: category4,
          user: user,
          notification_level: CategoryUser.notification_levels[:regular],
        )
        CategoryUser.create(
          category: Fabricate(:category),
          user: admin_user,
          notification_level: CategoryUser.notification_levels[:regular],
        )

        expect(json[:muted_category_ids]).to eq([category1.id])
        expect(json[:tracked_category_ids]).to eq([category2.id])
        expect(json[:watched_category_ids]).to eq([category3.id])
        expect(json[:regular_category_ids]).to eq([category4.id])
      end

      it "returns the user's tag notification levels, not the scope user's" do
        tag1 = Fabricate(:tag)
        tag2 = Fabricate(:tag)
        tag3 = Fabricate(:tag)
        tag4 = Fabricate(:tag)
        TagUser.create(
          tag: tag1,
          user: user,
          notification_level: TagUser.notification_levels[:muted],
        )
        TagUser.create(
          tag: Fabricate(:tag),
          user: admin_user,
          notification_level: TagUser.notification_levels[:muted],
        )
        TagUser.create(
          tag: tag2,
          user: user,
          notification_level: TagUser.notification_levels[:tracking],
        )
        TagUser.create(
          tag: Fabricate(:tag),
          user: admin_user,
          notification_level: TagUser.notification_levels[:tracking],
        )
        TagUser.create(
          tag: tag3,
          user: user,
          notification_level: TagUser.notification_levels[:watching],
        )
        TagUser.create(
          tag: Fabricate(:tag),
          user: admin_user,
          notification_level: TagUser.notification_levels[:watching],
        )
        TagUser.create(
          tag: tag4,
          user: user,
          notification_level: TagUser.notification_levels[:watching_first_post],
        )
        TagUser.create(
          tag: Fabricate(:tag),
          user: admin_user,
          notification_level: TagUser.notification_levels[:watching_first_post],
        )

        expect(json[:muted_tags]).to eq([tag1.name])
        expect(json[:tracked_tags]).to eq([tag2.name])
        expect(json[:watched_tags]).to eq([tag3.name])
        expect(json[:watching_first_post_tags]).to eq([tag4.name])
      end
    end

    context "with `enable_names` true" do
      before { SiteSetting.enable_names = true }

      it "has a name" do
        expect(json[:name]).to be_present
      end
    end

    context "with `enable_names` false" do
      before { SiteSetting.enable_names = false }

      it "has a name" do
        expect(json[:name]).to be_blank
      end
    end

    context "with filled out backgrounds" do
      before do
        user.user_profile.upload_card_background(upload)
        user.user_profile.upload_profile_background(upload2)
      end

      it "has a profile background" do
        expect(json[:card_background_upload_url]).to eq(upload.url)
        expect(json[:profile_background_upload_url]).to eq(upload2.url)
      end
    end

    context "with filled out website" do
      context "when website has a path" do
        before { user.user_profile.website = "http://example.com/user" }

        it "has a website with a path" do
          expect(json[:website]).to eq "http://example.com/user"
        end

        it "returns complete website name with path" do
          expect(json[:website_name]).to eq "example.com/user"
        end
      end

      context "when website has a subdomain" do
        before { user.user_profile.website = "http://subdomain.example.com/user" }

        it "has a website with a subdomain" do
          expect(json[:website]).to eq "http://subdomain.example.com/user"
        end

        it "returns website name with the subdomain" do
          expect(json[:website_name]).to eq "subdomain.example.com/user"
        end
      end

      context "when website has www" do
        before { user.user_profile.website = "http://www.example.com/user" }

        it "has a website with the www" do
          expect(json[:website]).to eq "http://www.example.com/user"
        end

        it "returns website name without the www" do
          expect(json[:website_name]).to eq "example.com/user"
        end
      end

      context "when website includes query parameters" do
        before { user.user_profile.website = "http://example.com/user?ref=payme" }

        it "has a website with query params" do
          expect(json[:website]).to eq "http://example.com/user?ref=payme"
        end

        it "has a website name without query params" do
          expect(json[:website_name]).to eq "example.com/user"
        end
      end

      context "when website is not a valid url" do
        before { user.user_profile.website = "invalid-url" }

        it "has a website with the invalid url" do
          expect(json[:website]).to eq "invalid-url"
        end

        it "has a nil website name" do
          expect(json[:website_name]).to eq nil
        end
      end
    end

    context "with filled out bio" do
      before do
        user.user_profile.bio_raw = "my raw bio"
        user.user_profile.bio_cooked = "my cooked bio"
      end

      it "has a bio" do
        expect(json[:bio_raw]).to eq "my raw bio"
      end

      it "has a cooked bio" do
        expect(json[:bio_cooked]).to eq "my cooked bio"
      end
    end

    describe "second_factor_enabled" do
      let(:scope) { Guardian.new(user) }
      it "is false by default" do
        expect(json[:second_factor_enabled]).to eq(false)
      end

      context "when totp enabled" do
        before { User.any_instance.stubs(:totp_enabled?).returns(true) }

        it "is true" do
          expect(json[:second_factor_enabled]).to eq(true)
        end
      end

      context "when security_keys enabled" do
        before { User.any_instance.stubs(:security_keys_enabled?).returns(true) }

        it "is true" do
          expect(json[:second_factor_enabled]).to eq(true)
        end
      end
    end

    describe "ignored and muted" do
      fab!(:viewing_user) { Fabricate(:user) }
      let(:scope) { Guardian.new(viewing_user) }

      it "returns false values for muted and ignored" do
        expect(json[:ignored]).to eq(false)
        expect(json[:muted]).to eq(false)
      end

      context "when ignored" do
        before do
          Fabricate(:ignored_user, user: viewing_user, ignored_user: user)
          viewing_user.reload
        end

        it "returns true for ignored" do
          expect(json[:ignored]).to eq(true)
          expect(json[:muted]).to eq(false)
        end
      end

      context "when muted" do
        before do
          Fabricate(:muted_user, user: viewing_user, muted_user: user)
          viewing_user.reload
        end

        it "returns true for muted" do
          expect(json[:muted]).to eq(true)
          expect(json[:ignored]).to eq(false)
        end
      end
    end

    describe "with a custom notification schedule" do
      let(:schedule) do
        UserNotificationSchedule.create({ user: user }.merge(UserNotificationSchedule::DEFAULT))
      end
      let(:scope) { Guardian.new(user) }

      it "includes the serialized schedule" do
        expect(json[:user_notification_schedule][:enabled]).to eq(schedule[:enabled])
        expect(json[:user_notification_schedule][:day_0_start_time]).to eq(
          schedule[:day_0_start_time],
        )
        expect(json[:user_notification_schedule][:day_6_end_time]).to eq(schedule[:day_6_end_time])
      end
    end
  end

  context "with custom_fields" do
    fab!(:user)
    let(:json) { UserSerializer.new(user, scope: Guardian.new, root: false).as_json }

    before do
      user.custom_fields["secret_field"] = "Only for me to know"
      user.custom_fields["public_field"] = "Everyone look here"
      user.save
    end

    it "doesn't serialize the fields by default" do
      json[:custom_fields]
      expect(json[:custom_fields]).to be_empty
    end

    it "serializes the fields listed in public_user_custom_fields site setting" do
      SiteSetting.public_user_custom_fields = "public_field"
      expect(json[:custom_fields]["public_field"]).to eq(user.custom_fields["public_field"])
      expect(json[:custom_fields]["secret_field"]).to eq(nil)
    end

    context "with user custom field" do
      before do
        plugin = Plugin::Instance.new
        plugin.allow_public_user_custom_field :public_field
      end

      after { DiscoursePluginRegistry.reset! }

      it "serializes the fields listed in public_user_custom_fields" do
        expect(json[:custom_fields]["public_field"]).to eq(user.custom_fields["public_field"])
        expect(json[:custom_fields]["secret_field"]).to eq(nil)
      end
    end
  end

  context "with user fields" do
    fab!(:user)

    let! :fields do
      [
        Fabricate(:user_field),
        Fabricate(:user_field),
        Fabricate(:user_field, show_on_profile: true),
        Fabricate(:user_field, show_on_user_card: true),
        Fabricate(:user_field, show_on_user_card: true, show_on_profile: true),
      ]
    end

    let(:other_user_json) do
      UserSerializer.new(user, scope: Guardian.new(Fabricate(:user)), root: false).as_json
    end
    let(:self_json) { UserSerializer.new(user, scope: Guardian.new(user), root: false).as_json }
    let(:admin_json) do
      UserSerializer.new(user, scope: Guardian.new(Fabricate(:admin)), root: false).as_json
    end

    it "includes the correct fields for each audience" do
      expect(admin_json[:user_fields].keys).to contain_exactly(*fields.map { |f| f.id.to_s })
      expect(other_user_json[:user_fields].keys).to contain_exactly(
        *fields[2..5].map { |f| f.id.to_s },
      )
      expect(self_json[:user_fields].keys).to contain_exactly(*fields.map { |f| f.id.to_s })
    end
  end

  context "with user_api_keys" do
    fab!(:user)

    it "sorts keys by last used time" do
      freeze_time

      user_api_key_0 =
        Fabricate(
          :readonly_user_api_key,
          user: user,
          last_used_at: 2.days.ago,
          revoked_at: Time.zone.now,
        )
      user_api_key_1 = Fabricate(:readonly_user_api_key, user: user, last_used_at: 7.days.ago)
      user_api_key_2 = Fabricate(:readonly_user_api_key, user: user, last_used_at: 1.days.ago)
      user_api_key_3 =
        Fabricate(
          :readonly_user_api_key,
          user: user,
          last_used_at: 4.days.ago,
          revoked_at: Time.zone.now,
        )
      user_api_key_4 = Fabricate(:readonly_user_api_key, user: user, last_used_at: 3.days.ago)

      json = UserSerializer.new(user, scope: Guardian.new(user), root: false).as_json

      expect(json[:user_api_keys].size).to eq(3)
      expect(json[:user_api_keys][0][:id]).to eq(user_api_key_1.id)
      expect(json[:user_api_keys][1][:id]).to eq(user_api_key_4.id)
      expect(json[:user_api_keys][2][:id]).to eq(user_api_key_2.id)
    end
  end

  context "with user_passkeys" do
    fab!(:user)
    fab!(:passkey0) do
      Fabricate(:passkey_with_random_credential, user: user, created_at: 5.hours.ago)
    end
    fab!(:passkey1) do
      Fabricate(:passkey_with_random_credential, user: user, created_at: 2.hours.ago)
    end

    it "does not include them if feature is disabled" do
      SiteSetting.enable_passkeys = false
      json = UserSerializer.new(user, scope: Guardian.new(user), root: false).as_json

      expect(json[:user_passkeys]).to eq(nil)
    end

    it "includes passkeys if feature is enabled" do
      SiteSetting.enable_passkeys = true

      json = UserSerializer.new(user, scope: Guardian.new(user), root: false).as_json

      expect(json[:user_passkeys][0][:id]).to eq(passkey0.id)
      expect(json[:user_passkeys][0][:name]).to eq(passkey0.name)
      expect(json[:user_passkeys][0][:last_used]).to eq(passkey0.last_used)
      expect(json[:user_passkeys][1][:id]).to eq(passkey1.id)
    end
  end

  context "for user sidebar attributes" do
    include_examples "User Sidebar Serializer Attributes", described_class

    it "does not include attributes when scoped to user that cannot edit user" do
      user2 = Fabricate(:user)
      serializer = described_class.new(user, scope: Guardian.new(user2), root: false)

      expect(serializer.as_json[:sidebar_category_ids]).to eq(nil)
      expect(serializer.as_json[:sidebar_tags]).to eq(nil)
      expect(serializer.as_json[:display_sidebar_tags]).to eq(nil)
    end
  end

  context "with groups" do
    fab!(:group) do
      Fabricate(
        :group,
        visibility_level: Group.visibility_levels[:public],
        members_visibility_level: Group.visibility_levels[:owners],
      )
    end
    let(:serializer) { UserSerializer.new(user, scope: Guardian.new, root: false) }

    before do
      group.add(user)
      group.save!
    end

    it "should show group even when members list is not visible" do
      json = serializer.as_json
      expect(json[:groups].length).to eq(1)
      expect(json[:groups].first[:id]).to eq(group.id)
    end
  end
end