# encoding: utf-8
# frozen_string_literal: true

RSpec.describe CategoryUser do
  fab!(:user)

  def tracking
    CategoryUser.notification_levels[:tracking]
  end

  def regular
    CategoryUser.notification_levels[:regular]
  end

  describe "#batch_set" do
    fab!(:category)

    def category_ids_at_level(level)
      CategoryUser.where(
        user_id: user.id,
        notification_level: CategoryUser.notification_levels[level],
      ).pluck(:category_id)
    end

    it "should add new records where required" do
      CategoryUser.batch_set(user, :watching, [category.id])

      expect(category_ids_at_level(:watching)).to eq([category.id])
    end

    it "should change existing records where required" do
      CategoryUser.create!(
        user_id: user.id,
        category_id: category.id,
        notification_level: CategoryUser.notification_levels[:muted],
      )

      CategoryUser.batch_set(user, :watching, [category.id])

      expect(category_ids_at_level(:watching)).to eq([category.id])
      expect(category_ids_at_level(:muted)).to eq([])
    end

    it "should delete extraneous records where required" do
      CategoryUser.create!(
        user_id: user.id,
        category_id: category.id,
        notification_level: CategoryUser.notification_levels[:watching],
      )

      CategoryUser.batch_set(user, :watching, [])

      expect(category_ids_at_level(:watching)).to eq([])
    end

    it "should return true when something changed" do
      expect(CategoryUser.batch_set(user, :watching, [category.id])).to eq(true)
    end

    it "should return false when nothing changed" do
      CategoryUser.batch_set(user, :watching, [category.id])

      expect(CategoryUser.batch_set(user, :watching, [category.id])).to eq(false)
    end
  end

  it "should correctly auto_track" do
    tracking_user = Fabricate(:user)
    topic = Fabricate(:post).topic

    TopicUser.change(user.id, topic.id, total_msecs_viewed: 10)
    TopicUser.change(tracking_user.id, topic.id, total_msecs_viewed: 10)

    CategoryUser.create!(
      user: tracking_user,
      category: topic.category,
      notification_level: tracking,
    )
    CategoryUser.auto_track(user_id: tracking_user.id)

    expect(TopicUser.get(topic, tracking_user).notification_level).to eq(tracking)
    expect(TopicUser.get(topic, user).notification_level).to eq(regular)
  end

  it "allows updating notification level" do
    category = Fabricate(:category)

    CategoryUser.set_notification_level_for_category(
      user,
      NotificationLevels.all[:watching_first_post],
      category.id,
    )

    expect(
      CategoryUser.where(
        user_id: user.id,
        category_id: category.id,
        notification_level: NotificationLevels.all[:watching_first_post],
      ).exists?,
    ).to eq(true)

    CategoryUser.set_notification_level_for_category(
      user,
      NotificationLevels.all[:regular],
      category.id,
    )

    expect(
      CategoryUser.where(
        user_id: user.id,
        category_id: category.id,
        notification_level: NotificationLevels.all[:regular],
      ).exists?,
    ).to eq(true)
  end

  describe "integration" do
    before do
      Jobs.run_immediately!
      NotificationEmailer.enable
    end

    it "should operate correctly" do
      watched_category = Fabricate(:category)
      muted_category = Fabricate(:category)
      tracked_category = Fabricate(:category)

      early_watched_post = create_post(category: watched_category)

      CategoryUser.create!(
        user: user,
        category: watched_category,
        notification_level: CategoryUser.notification_levels[:watching],
      )
      CategoryUser.create!(
        user: user,
        category: muted_category,
        notification_level: CategoryUser.notification_levels[:muted],
      )
      CategoryUser.create!(
        user: user,
        category: tracked_category,
        notification_level: CategoryUser.notification_levels[:tracking],
      )

      watched_post = create_post(category: watched_category)
      _muted_post = create_post(category: muted_category)
      tracked_post = create_post(category: tracked_category)

      create_post(topic_id: early_watched_post.topic_id)

      expect(Notification.where(user_id: user.id, topic_id: watched_post.topic_id).count).to eq 1
      expect(
        Notification.where(user_id: user.id, topic_id: early_watched_post.topic_id).count,
      ).to eq 1
      expect(Notification.where(user_id: user.id, topic_id: tracked_post.topic_id).count).to eq 0

      # we must create a record so tracked flicks over
      TopicUser.change(user.id, tracked_post.topic_id, total_msecs_viewed: 10)
      tu = TopicUser.get(tracked_post.topic, user)
      expect(tu.notification_level).to eq TopicUser.notification_levels[:tracking]
      expect(tu.notifications_reason_id).to eq TopicUser.notification_reasons[:auto_track_category]
    end

    it "topics that move to a tracked category should auto track" do
      first_post = create_post
      tracked_category = first_post.topic.category

      TopicUser.change(user.id, first_post.topic_id, total_msecs_viewed: 10)
      tu = TopicUser.get(first_post.topic, user)
      expect(tu.notification_level).to eq TopicUser.notification_levels[:regular]

      CategoryUser.set_notification_level_for_category(
        user,
        CategoryUser.notification_levels[:tracking],
        tracked_category.id,
      )

      tu = TopicUser.get(first_post.topic, user)
      expect(tu.notification_level).to eq TopicUser.notification_levels[:tracking]
    end

    it "unwatches categories that have been changed" do
      watched_category = Fabricate(:category)
      CategoryUser.create!(
        user: user,
        category: watched_category,
        notification_level: CategoryUser.notification_levels[:watching],
      )

      post = create_post(category: watched_category)
      tu = TopicUser.get(post.topic, user)

      # we start watching cause a notification is sent to the watching user
      # this position sent is tracking in topic users
      expect(tu.notification_level).to eq TopicUser.notification_levels[:watching]

      # Now, change the topic's category
      unwatched_category = Fabricate(:category)
      post.topic.change_category_to_id(unwatched_category.id)
      expect(
        TopicUser.get(post.topic, user).notification_level,
      ).to eq TopicUser.notification_levels[:tracking]
    end

    it "does not delete TopicUser record when topic category is changed, and new category has same notification level" do
      # this is done so as to maintain topic notification state when topic category is changed and the new category has same notification level for the user
      # see: https://meta.discourse.org/t/changing-topic-from-one-watched-category-to-another-watched-category-makes-topic-new-again/36517/15

      watched_category_1 = Fabricate(:category)
      watched_category_2 = Fabricate(:category)
      category_3 = Fabricate(:category)

      post = create_post(category: watched_category_1)

      CategoryUser.create!(
        user: user,
        category: watched_category_1,
        notification_level: CategoryUser.notification_levels[:watching],
      )
      CategoryUser.create!(
        user: user,
        category: watched_category_2,
        notification_level: CategoryUser.notification_levels[:watching],
      )

      # we must have a topic user record otherwise it will be watched implicitly
      TopicUser.change(user.id, post.topic_id, total_msecs_viewed: 10)

      expect(
        TopicUser.get(post.topic, user).notification_level,
      ).to eq TopicUser.notification_levels[:watching]

      post.topic.change_category_to_id(category_3.id)
      expect(
        TopicUser.get(post.topic, user).notification_level,
      ).to eq TopicUser.notification_levels[:tracking]

      post.topic.change_category_to_id(watched_category_2.id)
      expect(
        TopicUser.get(post.topic, user).notification_level,
      ).to eq TopicUser.notification_levels[:watching]

      post.topic.change_category_to_id(watched_category_1.id)
      expect(
        TopicUser.get(post.topic, user).notification_level,
      ).to eq TopicUser.notification_levels[:watching]
    end

    it "deletes TopicUser record when topic category is changed, and new category has different notification level" do
      watched_category = Fabricate(:category)
      tracked_category = Fabricate(:category)
      CategoryUser.create!(
        user: user,
        category: watched_category,
        notification_level: CategoryUser.notification_levels[:watching],
      )
      CategoryUser.create!(
        user: user,
        category: tracked_category,
        notification_level: CategoryUser.notification_levels[:tracking],
      )

      post = create_post(category: watched_category)
      tu = TopicUser.get(post.topic, user)
      expect(tu.notification_level).to eq TopicUser.notification_levels[:watching]

      # Now, change the topic's category
      post.topic.change_category_to_id(tracked_category.id)
      expect(
        TopicUser.get(post.topic, user).notification_level,
      ).to eq TopicUser.notification_levels[:tracking]
    end

    it "is destroyed when a user is deleted" do
      category = Fabricate(:category)

      CategoryUser.create!(
        user: user,
        category: category,
        notification_level: CategoryUser.notification_levels[:watching],
      )

      expect(CategoryUser.where(user_id: user.id).count).to eq(1)

      user.destroy!

      expect(CategoryUser.where(user_id: user.id).count).to eq(0)
    end
  end

  describe "#notification_levels_for" do
    let!(:category1) { Fabricate(:category) }
    let!(:category2) { Fabricate(:category) }
    let!(:category3) { Fabricate(:category) }
    let!(:category4) { Fabricate(:category) }
    let!(:category5) { Fabricate(:category) }

    context "for anon" do
      let(:user) { nil }
      before do
        SiteSetting.default_categories_watching = category1.id.to_s
        SiteSetting.default_categories_tracking = category2.id.to_s
        SiteSetting.default_categories_watching_first_post = category3.id.to_s
        SiteSetting.default_categories_normal = category4.id.to_s
        SiteSetting.default_categories_muted = category5.id.to_s
      end
      it "every category from the default_categories_* site settings get overridden to regular, except for muted" do
        levels = CategoryUser.notification_levels_for(user)
        expect(levels[category1.id]).to eq(CategoryUser.notification_levels[:regular])
        expect(levels[category2.id]).to eq(CategoryUser.notification_levels[:regular])
        expect(levels[category3.id]).to eq(CategoryUser.notification_levels[:regular])
        expect(levels[category4.id]).to eq(CategoryUser.notification_levels[:regular])
        expect(levels[category5.id]).to eq(CategoryUser.notification_levels[:muted])
      end
    end

    context "for a user" do
      before do
        CategoryUser.create(
          user: user,
          category: category1,
          notification_level: CategoryUser.notification_levels[:watching],
        )
        CategoryUser.create(
          user: user,
          category: category2,
          notification_level: CategoryUser.notification_levels[:tracking],
        )
        CategoryUser.create(
          user: user,
          category: category3,
          notification_level: CategoryUser.notification_levels[:watching_first_post],
        )
        CategoryUser.create(
          user: user,
          category: category4,
          notification_level: CategoryUser.notification_levels[:regular],
        )
        CategoryUser.create(
          user: user,
          category: category5,
          notification_level: CategoryUser.notification_levels[:muted],
        )
      end
      it "gets the category_user notification levels for all categories the user is tracking and does not
      include categories the user is not tracking at all" do
        category6 = Fabricate(:category)
        levels = CategoryUser.notification_levels_for(user)
        expect(levels[category1.id]).to eq(CategoryUser.notification_levels[:watching])
        expect(levels[category2.id]).to eq(CategoryUser.notification_levels[:tracking])
        expect(levels[category3.id]).to eq(CategoryUser.notification_levels[:watching_first_post])
        expect(levels[category4.id]).to eq(CategoryUser.notification_levels[:regular])
        expect(levels[category5.id]).to eq(CategoryUser.notification_levels[:muted])
        expect(levels.key?(category6.id)).to eq(false)
      end
    end
  end

  describe ".muted_category_ids" do
    context "with max category nesting 2" do
      fab!(:category1) { Fabricate(:category) }
      fab!(:category2) { Fabricate(:category, parent_category: category1) }
      fab!(:category3) { Fabricate(:category, parent_category: category1) }

      it "calculates muted categories based on parent category state" do
        expect(CategoryUser.indirectly_muted_category_ids(user)).to eq([])
        expect(CategoryUser.muted_category_ids(user)).to eq([])

        category_user =
          CategoryUser.create!(
            user: user,
            category: category1,
            notification_level: CategoryUser.notification_levels[:muted],
          )
        expect(CategoryUser.indirectly_muted_category_ids(user)).to contain_exactly(
          category2.id,
          category3.id,
        )
        expect(CategoryUser.muted_category_ids(user)).to contain_exactly(
          category1.id,
          category2.id,
          category3.id,
        )

        CategoryUser.create!(
          user: user,
          category: category3,
          notification_level: CategoryUser.notification_levels[:muted],
        )
        expect(CategoryUser.indirectly_muted_category_ids(user)).to contain_exactly(category2.id)
        expect(CategoryUser.muted_category_ids(user)).to contain_exactly(
          category1.id,
          category2.id,
          category3.id,
        )

        category_user.update!(notification_level: CategoryUser.notification_levels[:regular])
        expect(CategoryUser.indirectly_muted_category_ids(user)).to eq([])
        expect(CategoryUser.muted_category_ids(user)).to contain_exactly(category3.id)
      end
    end

    context "with max category nesting 3" do
      let(:category1) { Fabricate(:category) }
      let(:category2) { Fabricate(:category, parent_category: category1) }
      let(:category3) { Fabricate(:category, parent_category: category2) }

      before do
        SiteSetting.max_category_nesting = 3
        category1
        category2
        category3
      end
      it "calculates muted categories based on parent category state" do
        expect(CategoryUser.indirectly_muted_category_ids(user)).to eq([])

        CategoryUser.create!(
          user: user,
          category: category1,
          notification_level: CategoryUser.notification_levels[:muted],
        )
        expect(CategoryUser.indirectly_muted_category_ids(user)).to contain_exactly(
          category2.id,
          category3.id,
        )
        expect(CategoryUser.muted_category_ids(user)).to contain_exactly(
          category1.id,
          category2.id,
          category3.id,
        )

        category_user3 =
          CategoryUser.create!(
            user: user,
            category: category3,
            notification_level: CategoryUser.notification_levels[:muted],
          )
        expect(CategoryUser.indirectly_muted_category_ids(user)).to contain_exactly(category2.id)
        expect(CategoryUser.muted_category_ids(user)).to contain_exactly(
          category1.id,
          category2.id,
          category3.id,
        )

        category_user3.destroy!
        category_user2 =
          CategoryUser.create!(
            user: user,
            category: category2,
            notification_level: CategoryUser.notification_levels[:muted],
          )
        expect(CategoryUser.indirectly_muted_category_ids(user)).to contain_exactly(category3.id)
        expect(CategoryUser.muted_category_ids(user)).to contain_exactly(
          category1.id,
          category2.id,
          category3.id,
        )

        category_user2.update!(notification_level: CategoryUser.notification_levels[:regular])
        expect(CategoryUser.indirectly_muted_category_ids(user)).to eq([])
        expect(CategoryUser.muted_category_ids(user)).to contain_exactly(category1.id)
      end
    end
  end
end