# frozen_string_literal: true

require 'rails_helper'

describe TopicTrackingState do

  fab!(:user) do
    Fabricate(:user)
  end

  let(:post) do
    create_post
  end

  let(:topic) { post.topic }
  fab!(:private_message_post) { Fabricate(:private_message_post) }
  let(:private_message_topic) { private_message_post.topic }

  describe '#publish_latest' do
    it 'can correctly publish latest' do
      message = MessageBus.track_publish("/latest") do
        described_class.publish_latest(topic)
      end.first

      data = message.data

      expect(data["topic_id"]).to eq(topic.id)
      expect(data["message_type"]).to eq(described_class::LATEST_MESSAGE_TYPE)
      expect(data["payload"]["archetype"]).to eq(Archetype.default)
    end

    describe 'private message' do
      it 'should not publish any message' do
        messages = MessageBus.track_publish do
          described_class.publish_latest(private_message_topic)
        end

        expect(messages).to eq([])
      end
    end
  end

  describe '#publish_unread' do
    it "can correctly publish unread" do
      message = MessageBus.track_publish(described_class.unread_channel_key(post.user.id)) do
        TopicTrackingState.publish_unread(post)
      end.first

      data = message.data

      expect(data["topic_id"]).to eq(topic.id)
      expect(data["message_type"]).to eq(described_class::UNREAD_MESSAGE_TYPE)
      expect(data["payload"]["archetype"]).to eq(Archetype.default)
    end

    describe 'for a private message' do
      before do
        TopicUser.change(
          private_message_topic.allowed_users.first.id,
          private_message_topic.id,
          notification_level: TopicUser.notification_levels[:tracking]
        )
      end

      it 'should not publish any message' do
        messages = MessageBus.track_publish do
          TopicTrackingState.publish_unread(private_message_post)
        end

        expect(messages).to eq([])
      end
    end
  end

  describe '#publish_muted' do
    let(:user) do
      Fabricate(:user, last_seen_at: Date.today)
    end
    let(:post) do
      create_post(user: user)
    end

    it 'can correctly publish muted' do
      TopicUser.find_by(topic: topic, user: post.user).update(notification_level: 0)
      messages = MessageBus.track_publish("/latest") do
        TopicTrackingState.publish_muted(topic)
      end

      muted_message = messages.find { |message| message.data["message_type"] == "muted" }

      expect(muted_message.data["topic_id"]).to eq(topic.id)
      expect(muted_message.data["message_type"]).to eq(described_class::MUTED_MESSAGE_TYPE)
    end

    it 'should not publish any message when notification level is not muted' do
      messages = MessageBus.track_publish("/latest") do
        TopicTrackingState.publish_muted(topic)
      end
      muted_messages = messages.select { |message| message.data["message_type"] == "muted" }

      expect(muted_messages).to eq([])
    end

    it 'should not publish any message when the user was not seen in the last 7 days' do
      TopicUser.find_by(topic: topic, user: post.user).update(notification_level: 0)
      post.user.update(last_seen_at: 8.days.ago)
      messages = MessageBus.track_publish("/latest") do
        TopicTrackingState.publish_muted(topic)
      end
      muted_messages = messages.select { |message| message.data["message_type"] == "muted" }
      expect(muted_messages).to eq([])
    end
  end

  describe '#publish_private_message' do
    fab!(:admin) { Fabricate(:admin) }

    describe 'normal topic' do
      it 'should publish the right message' do
        allowed_users = private_message_topic.allowed_users

        messages = MessageBus.track_publish do
          TopicTrackingState.publish_private_message(private_message_topic)
        end

        expect(messages.count).to eq(1)

        message = messages.first

        expect(message.channel).to eq('/private-messages/inbox')
        expect(message.data["topic_id"]).to eq(private_message_topic.id)
        expect(message.user_ids).to contain_exactly(*allowed_users.map(&:id))
      end
    end

    describe 'topic with groups' do
      fab!(:group1) { Fabricate(:group, users: [Fabricate(:user)]) }
      fab!(:group2) { Fabricate(:group, users: [Fabricate(:user), Fabricate(:user)]) }

      before do
        [group1, group2].each do |group|
          private_message_topic.allowed_groups << group
        end
      end

      it "should publish the right message" do
        messages = MessageBus.track_publish do
          TopicTrackingState.publish_private_message(
            private_message_topic
          )
        end

        expect(messages.map(&:channel)).to contain_exactly(
          '/private-messages/inbox',
          "/private-messages/group/#{group1.name}",
          "/private-messages/group/#{group2.name}"
        )

        message = messages.find do |m|
          m.channel == '/private-messages/inbox'
        end

        expect(message.data["topic_id"]).to eq(private_message_topic.id)
        expect(message.user_ids).to eq(private_message_topic.allowed_users.map(&:id))

        [group1, group2].each do |group|
          message = messages.find do |m|
            m.channel == "/private-messages/group/#{group.name}"
          end

          expect(message.data["topic_id"]).to eq(private_message_topic.id)
          expect(message.user_ids).to eq(group.users.map(&:id))
        end
      end

      describe "archiving topic" do
        it "should publish the right message" do
          messages = MessageBus.track_publish do
            TopicTrackingState.publish_private_message(
              private_message_topic,
              group_archive: true
            )
          end

          expect(messages.map(&:channel)).to contain_exactly(
            '/private-messages/inbox',
            "/private-messages/group/#{group1.name}",
            "/private-messages/group/#{group1.name}/archive",
            "/private-messages/group/#{group2.name}",
            "/private-messages/group/#{group2.name}/archive",
          )

          message = messages.find { |m| m.channel == '/private-messages/inbox' }

          expect(message.data["topic_id"]).to eq(private_message_topic.id)
          expect(message.user_ids).to eq(private_message_topic.allowed_users.map(&:id))

          [group1, group2].each do |group|
            group_channel = "/private-messages/group/#{group.name}"

            [
              group_channel,
              "#{group_channel}/archive"
            ].each do |channel|
              message = messages.find { |m| m.channel == channel }
              expect(message.data["topic_id"]).to eq(private_message_topic.id)
              expect(message.user_ids).to eq(group.users.map(&:id))
            end
          end
        end
      end
    end

    describe 'topic with new post' do
      let(:user) { private_message_topic.allowed_users.last }

      let!(:post) do
        Fabricate(:post,
          topic: private_message_topic,
          user: user
        )
      end

      let!(:group) do
        group = Fabricate(:group, users: [Fabricate(:user)])
        private_message_topic.allowed_groups << group
        group
      end

      it 'should publish the right message' do
        messages = MessageBus.track_publish do
          TopicTrackingState.publish_private_message(
            private_message_topic,
            post: post
          )
        end

        expected_channels = [
          '/private-messages/inbox',
          '/private-messages/sent',
          "/private-messages/group/#{group.name}"
        ]

        expect(messages.map(&:channel)).to contain_exactly(*expected_channels)

        expected_channels.zip([
          private_message_topic.allowed_users.map(&:id),
          [user.id],
          [group.users.first.id]
        ]).each do |channel, user_ids|
          message = messages.find { |m| m.channel == channel }

          expect(message.data["topic_id"]).to eq(private_message_topic.id)
          expect(message.user_ids).to eq(user_ids)
        end
      end
    end

    describe 'archived topic' do
      it 'should publish the right message' do
        messages = MessageBus.track_publish do
          TopicTrackingState.publish_private_message(
            private_message_topic,
            archive_user_id: private_message_post.user_id,
          )
        end

        expected_channels = [
          "/private-messages/archive",
          "/private-messages/inbox",
          "/private-messages/sent",
        ]

        expect(messages.map(&:channel)).to eq(expected_channels)

        expected_channels.each do |channel|
          message = messages.find { |m| m.channel = channel }
          expect(message.data["topic_id"]).to eq(private_message_topic.id)
          expect(message.user_ids).to eq([private_message_post.user_id])
        end
      end
    end

    describe 'for a regular topic' do
      it 'should not publish any message' do
        topic.allowed_users << Fabricate(:user)

        messages = MessageBus.track_publish do
          TopicTrackingState.publish_private_message(topic)
        end

        expect(messages).to eq([])
      end
    end
  end

  describe '#publish_read_private_message' do
    fab!(:group) { Fabricate(:group) }
    let(:read_topic_key) { "/private-messages/unread-indicator/#{group_message.id}" }
    let(:read_post_key) { "/topic/#{group_message.id}" }
    let(:latest_post_number) { 3 }
    let(:group_message) { Fabricate(:private_message_topic,
        allowed_groups: [group],
        topic_allowed_users: [Fabricate.build(:topic_allowed_user, user: user)],
        highest_post_number: latest_post_number
      )
    }
    let!(:post) {
      Fabricate(:post, topic: group_message, post_number: latest_post_number)
    }

    before do
      group.add(user)
    end

    it 'does not trigger a read count update if no allowed groups have the option enabled' do
      messages = MessageBus.track_publish(read_post_key) do
        TopicTrackingState.publish_read_indicator_on_read(group_message.id, latest_post_number, user.id)
      end

      expect(messages).to be_empty
    end

    context 'when the read indicator is enabled' do
      before { group.update!(publish_read_state: true) }

      it 'publishes a message to hide the unread indicator' do
        message = MessageBus.track_publish(read_topic_key) do
          TopicTrackingState.publish_read_indicator_on_read(group_message.id, latest_post_number, user.id)
        end.first

        expect(message.data['topic_id']).to eq group_message.id
        expect(message.data['show_indicator']).to eq false
      end

      it 'publishes a message to show the unread indicator when a non-member creates a new post' do
        allowed_user = Fabricate(:topic_allowed_user, topic: group_message)
        message = MessageBus.track_publish(read_topic_key) do
          TopicTrackingState.publish_read_indicator_on_write(group_message.id, latest_post_number, allowed_user.id)
        end.first

        expect(message.data['topic_id']).to eq group_message.id
        expect(message.data['show_indicator']).to eq true
      end

      it 'does not publish the unread indicator if the message is not the last one' do
        not_last_post_number = latest_post_number - 1
        Fabricate(:post, topic: group_message, post_number: not_last_post_number)
        messages = MessageBus.track_publish(read_topic_key) do
          TopicTrackingState.publish_read_indicator_on_read(group_message.id, not_last_post_number, user.id)
        end

        expect(messages).to be_empty
      end

      it 'does not publish the read indicator if the user is not a group member' do
        allowed_user = Fabricate(:topic_allowed_user, topic: group_message)
        messages = MessageBus.track_publish(read_topic_key) do
          TopicTrackingState.publish_read_indicator_on_read(group_message.id, latest_post_number, allowed_user.user_id)
        end

        expect(messages).to be_empty
      end

      it 'publish a read count update to every client' do
        message = MessageBus.track_publish(read_post_key) do
          TopicTrackingState.publish_read_indicator_on_read(group_message.id, latest_post_number, user.id)
        end.first

        expect(message.data[:type]).to eq :read
      end
    end
  end

  it "correctly handles muted categories" do

    user = Fabricate(:user)
    post

    report = TopicTrackingState.report(user)
    expect(report.length).to eq(1)

    CategoryUser.create!(user_id: user.id,
                         notification_level: CategoryUser.notification_levels[:muted],
                         category_id: post.topic.category_id
                         )

    create_post(topic_id: post.topic_id)

    report = TopicTrackingState.report(user)
    expect(report.length).to eq(0)

    TopicUser.create!(user_id: user.id, topic_id: post.topic_id, last_read_post_number: 1, notification_level: 3)

    report = TopicTrackingState.report(user)
    expect(report.length).to eq(1)
  end

  it "correctly handles category_users with null notification level" do
    user = Fabricate(:user)
    post

    report = TopicTrackingState.report(user)
    expect(report.length).to eq(1)

    CategoryUser.create!(user_id: user.id, category_id: post.topic.category_id)

    report = TopicTrackingState.report(user)
    expect(report.length).to eq(1)
  end

  it "works when categories are default muted" do
    SiteSetting.mute_all_categories_by_default = true

    user = Fabricate(:user)
    post

    report = TopicTrackingState.report(user)
    expect(report.length).to eq(0)

    CategoryUser.create!(user_id: user.id,
                         notification_level: CategoryUser.notification_levels[:regular],
                         category_id: post.topic.category_id
                         )

    create_post(topic_id: post.topic_id)

    report = TopicTrackingState.report(user)
    expect(report.length).to eq(1)
  end

  context 'muted tags' do
    it "remove_muted_tags_from_latest is set to always" do
      SiteSetting.remove_muted_tags_from_latest = 'always'
      user = Fabricate(:user)
      tag1 = Fabricate(:tag)
      tag2 = Fabricate(:tag)
      Fabricate(:topic_tag, tag: tag1, topic: topic)
      Fabricate(:topic_tag, tag: tag2, topic: topic)
      post

      report = TopicTrackingState.report(user)
      expect(report.length).to eq(1)

      TagUser.create!(user_id: user.id,
                      notification_level: TagUser.notification_levels[:muted],
                      tag_id: tag1.id
                     )

      report = TopicTrackingState.report(user)
      expect(report.length).to eq(0)

      TopicTag.where(topic_id: topic.id).delete_all

      report = TopicTrackingState.report(user)
      expect(report.length).to eq(1)
    end

    it "remove_muted_tags_from_latest is set to only_muted" do
      SiteSetting.remove_muted_tags_from_latest = 'only_muted'
      user = Fabricate(:user)
      tag1 = Fabricate(:tag)
      tag2 = Fabricate(:tag)
      Fabricate(:topic_tag, tag: tag1, topic: topic)
      Fabricate(:topic_tag, tag: tag2, topic: topic)
      post

      report = TopicTrackingState.report(user)
      expect(report.length).to eq(1)

      TagUser.create!(user_id: user.id,
                      notification_level: TagUser.notification_levels[:muted],
                      tag_id: tag1.id
                     )

      report = TopicTrackingState.report(user)
      expect(report.length).to eq(1)

      TagUser.create!(user_id: user.id,
                      notification_level: TagUser.notification_levels[:muted],
                      tag_id: tag2.id
                     )

      report = TopicTrackingState.report(user)
      expect(report.length).to eq(0)

      TopicTag.where(topic_id: topic.id).delete_all

      report = TopicTrackingState.report(user)
      expect(report.length).to eq(1)
    end

    it "remove_muted_tags_from_latest is set to never" do
      SiteSetting.remove_muted_tags_from_latest = 'never'
      user = Fabricate(:user)
      tag1 = Fabricate(:tag)
      Fabricate(:topic_tag, tag: tag1, topic: topic)
      post

      report = TopicTrackingState.report(user)
      expect(report.length).to eq(1)

      TagUser.create!(user_id: user.id,
                      notification_level: TagUser.notification_levels[:muted],
                      tag_id: tag1.id
                     )

      report = TopicTrackingState.report(user)
      expect(report.length).to eq(1)
    end
  end

  it "correctly handles seen categories" do
    freeze_time 1.minute.ago
    user = Fabricate(:user)
    post

    report = TopicTrackingState.report(user)
    expect(report.length).to eq(1)

    CategoryUser.create!(user_id: user.id,
                         notification_level: CategoryUser.notification_levels[:regular],
                         category_id: post.topic.category_id,
                         last_seen_at: post.topic.created_at
                         )

    report = TopicTrackingState.report(user)
    expect(report.length).to eq(0)

    unfreeze_time
    post.topic.touch(:created_at)

    report = TopicTrackingState.report(user)
    expect(report.length).to eq(1)
  end

  it "correctly handles capping" do
    user = Fabricate(:user)

    post1 = create_post
    Fabricate(:post, topic: post1.topic)

    post2 = create_post
    Fabricate(:post, topic: post2.topic)

    post3 = create_post
    Fabricate(:post, topic: post3.topic)

    tracking = {
      notification_level: TopicUser.notification_levels[:tracking],
      last_read_post_number: 1,
      highest_seen_post_number: 1
    }

    TopicUser.change(user.id, post1.topic_id, tracking)
    TopicUser.change(user.id, post2.topic_id, tracking)
    TopicUser.change(user.id, post3.topic_id, tracking)

    report = TopicTrackingState.report(user)
    expect(report.length).to eq(3)

  end

  context "tag support" do
    after do
      # this is a bit of an odd hook, but this is a global change
      # used by plugins that leverage tagging heavily and need
      # tag information in topic tracking state
      TopicTrackingState.include_tags_in_report = false
    end

    it "correctly handles tags" do
      SiteSetting.tagging_enabled = true

      post.topic.notifier.watch_topic!(post.topic.user_id)

      DiscourseTagging.tag_topic_by_names(
        post.topic,
        Guardian.new(Discourse.system_user),
        ['bananas', 'apples']
      )

      TopicTrackingState.include_tags_in_report = true

      report = TopicTrackingState.report(user)
      expect(report.length).to eq(1)
      row = report[0]
      expect(row.tags).to contain_exactly("apples", "bananas")

      TopicTrackingState.include_tags_in_report = false
      SiteSetting.show_filter_by_tag = true

      report = TopicTrackingState.report(user)
      expect(report.length).to eq(1)
      row = report[0]
      expect(row.tags).to contain_exactly("apples", "bananas")

      SiteSetting.show_filter_by_tag = false

      report = TopicTrackingState.report(user)
      expect(report.length).to eq(1)
      row = report[0]
      expect(row.respond_to? :tags).to eq(false)
    end
  end

  it "correctly gets the tracking state" do
    report = TopicTrackingState.report(user)
    expect(report.length).to eq(0)

    post.topic.notifier.watch_topic!(post.topic.user_id)

    report = TopicTrackingState.report(user)

    expect(report.length).to eq(1)
    row = report[0]

    expect(row.topic_id).to eq(post.topic_id)
    expect(row.highest_post_number).to eq(1)
    expect(row.last_read_post_number).to eq(nil)
    expect(row.user_id).to eq(user.id)

    # lets not leak out random users
    expect(TopicTrackingState.report(post.user)).to be_empty

    # lets not return anything if we scope on non-existing topic
    expect(TopicTrackingState.report(user, post.topic_id + 1)).to be_empty

    # when we reply the poster should have an unread row
    create_post(user: user, topic: post.topic)

    report = TopicTrackingState.report(user)
    expect(report.length).to eq(0)

    report = TopicTrackingState.report(post.user)
    expect(report.length).to eq(1)

    row = report[0]

    expect(row.topic_id).to eq(post.topic_id)
    expect(row.highest_post_number).to eq(2)
    expect(row.last_read_post_number).to eq(1)
    expect(row.user_id).to eq(post.user_id)

    # when we have no permission to see a category, don't show its stats
    category = Fabricate(:category, read_restricted: true)

    post.topic.category_id = category.id
    post.topic.save

    expect(TopicTrackingState.report(post.user)).to be_empty
    expect(TopicTrackingState.report(user)).to be_empty
  end
end