# frozen_string_literal: true

RSpec.describe UserNotifications do
  let(:user) { Fabricate(:admin) }

  describe "#get_context_posts" do
    it "does not include hidden/deleted/user_deleted posts in context" do
      post1 = create_post
      _post2 = Fabricate(:post, topic: post1.topic, deleted_at: 1.day.ago)
      _post3 = Fabricate(:post, topic: post1.topic, user_deleted: true)
      _post4 = Fabricate(:post, topic: post1.topic, hidden: true)
      _post5 = Fabricate(:post, topic: post1.topic, post_type: Post.types[:moderator_action])
      _post6 = Fabricate(:post, topic: post1.topic, post_type: Post.types[:small_action])
      _post7 = Fabricate(:post, topic: post1.topic, post_type: Post.types[:whisper])
      last = Fabricate(:post, topic: post1.topic)

      post1.user.user_option.email_previous_replies = UserOption.previous_replies_type[:always]

      # default is only post #1
      expect(UserNotifications.get_context_posts(last, nil, post1.user).count).to eq(1)
      # staff members can also see the whisper
      moderator = build(:moderator)
      moderator.user_option = UserOption.new
      moderator.user_option.email_previous_replies = UserOption.previous_replies_type[:always]
      tu = TopicUser.new(topic: post1.topic, user: moderator)
      expect(UserNotifications.get_context_posts(last, tu, tu.user).count).to eq(2)
    end

    it "allows users to control context" do
      post1 = create_post
      _post2 = Fabricate(:post, topic: post1.topic)
      post3 = Fabricate(:post, topic: post1.topic)

      user = Fabricate(:user)
      TopicUser.change(user.id, post1.topic_id, last_emailed_post_number: 1)
      topic_user = TopicUser.find_by(user_id: user.id, topic_id: post1.topic_id)
      # to avoid reloads after update_columns
      user = topic_user.user
      user.user_option.update_columns(
        email_previous_replies: UserOption.previous_replies_type[:unless_emailed],
      )

      expect(UserNotifications.get_context_posts(post3, topic_user, user).count).to eq(1)

      user.user_option.update_columns(
        email_previous_replies: UserOption.previous_replies_type[:never],
      )
      expect(UserNotifications.get_context_posts(post3, topic_user, user).count).to eq(0)

      user.user_option.update_columns(
        email_previous_replies: UserOption.previous_replies_type[:always],
      )
      expect(UserNotifications.get_context_posts(post3, topic_user, user).count).to eq(2)

      SiteSetting.private_email = true
      expect(UserNotifications.get_context_posts(post3, topic_user, user).count).to eq(0)
    end
  end

  describe ".signup" do
    subject(:email) { UserNotifications.signup(user) }

    it "works" do
      expect(email.to).to eq([user.email])
      expect(email.subject).to be_present
      expect(email.from).to eq([SiteSetting.notification_email])
      expect(email.body).to be_present
    end
  end

  describe ".forgot_password" do
    subject(:email) { UserNotifications.forgot_password(user) }

    it "works" do
      expect(email.to).to eq([user.email])
      expect(email.subject).to be_present
      expect(email.from).to eq([SiteSetting.notification_email])
      expect(email.body).to be_present
    end
  end

  describe ".post_approved" do
    fab!(:post)

    it "works" do
      subject =
        UserNotifications.post_approved(user, { notification_data_hash: { post_url: post.url } })

      expect(subject.to).to eq([user.email])
      expect(subject.subject).to be_present
      expect(subject.from).to eq([SiteSetting.notification_email])
      expect(subject.body).to be_present
    end
  end

  describe ".confirm_new_email" do
    let(:opts) { { requested_by_admin: requested_by_admin, email_token: token } }
    let(:token) { "test123" }

    context "when requested by admin" do
      let(:requested_by_admin) { true }

      it "uses the requested by admin template" do
        expect(UserNotifications.confirm_new_email(user, opts).body).to include(
          "This email change was requested by a site admin.",
        )
      end
    end

    context "when not requested by admin" do
      let(:requested_by_admin) { false }

      it "uses the normal template" do
        expect(UserNotifications.confirm_new_email(user, opts).body).not_to include(
          "This email change was requested by a site admin.",
        )
      end
    end
  end

  describe ".email_login" do
    subject(:email) { UserNotifications.email_login(user, email_token: email_token) }

    let(:email_token) do
      Fabricate(:email_token, user: user, scope: EmailToken.scopes[:email_login]).token
    end

    it "generates the right email" do
      expect(email.to).to eq([user.email])
      expect(email.from).to eq([SiteSetting.notification_email])

      expect(email.subject).to eq(
        I18n.t("user_notifications.email_login.subject_template", email_prefix: SiteSetting.title),
      )

      expect(email.body.to_s).to match(
        I18n.t(
          "user_notifications.email_login.text_body_template",
          site_name: SiteSetting.title,
          base_url: Discourse.base_url,
          email_token: email_token,
        ),
      )
    end
  end

  describe ".digest" do
    subject(:email) { UserNotifications.digest(user) }

    after { Discourse.redis.keys("summary-new-users:*").each { |key| Discourse.redis.del(key) } }

    context "without new topics" do
      it "doesn't send the email" do
        expect(email.to).to be_blank
      end
    end

    context "with topics only from new users" do
      let!(:new_today) do
        Fabricate(
          :topic,
          user: Fabricate(:user, trust_level: TrustLevel[0], created_at: 10.minutes.ago),
          title: "Hey everyone look at me",
        )
      end
      let!(:new_yesterday) do
        Fabricate(
          :topic,
          user: Fabricate(:user, trust_level: TrustLevel[0], created_at: 25.hours.ago),
          created_at: 25.hours.ago,
          title: "This topic is of interest to you",
        )
      end

      it "returns topics from new users if they're more than 24 hours old" do
        expect(email.to).to eq([user.email])
        html = email.html_part.body.to_s
        expect(html).to include(new_yesterday.title)
        expect(html).to_not include(new_today.title)
      end
    end

    context "with new topics" do
      fab!(:coding_horror)

      let!(:popular_topic) { Fabricate(:topic, user: coding_horror, created_at: 1.hour.ago) }

      let!(:another_popular_topic) do
        Fabricate(:topic, user: coding_horror, created_at: 1.hour.ago)
      end

      let!(:post) { Fabricate(:post, topic: popular_topic, post_number: 1) }

      let!(:another_post) { Fabricate(:post, topic: another_popular_topic, post_number: 1) }

      it "works" do
        expect(email.to).to eq([user.email])
        expect(email.subject).to be_present
        expect(email.from).to eq([SiteSetting.notification_email])
        expect(email.html_part.body.to_s).to be_present
        expect(email.text_part.body.to_s).to be_present
        expect(email.header["List-Unsubscribe"].to_s).to match(/\/email\/unsubscribe\/\h{64}/)
        expect(email.header["X-Discourse-Topic-Ids"].to_s).to eq(
          "#{another_popular_topic.id},#{popular_topic.id}",
        )
        expect(email.header["X-Discourse-Post-Ids"].to_s).to eq("#{another_post.id},#{post.id}")
        expect(email.html_part.body.to_s).to include("New Users")
      end

      it "doesn't include new user count if digest_after_minutes is low" do
        user.user_option.digest_after_minutes = 60
        expect(email.html_part.body.to_s).to_not include("New Users")
      end

      it "works with min_date string" do
        digest = UserNotifications.digest(user, since: 1.month.ago.to_date.to_s)
        expect(digest.html_part.body.to_s).to be_present
        expect(digest.text_part.body.to_s).to be_present
        expect(digest.html_part.body.to_s).to include("New Users")
      end

      it "includes email_prefix in email subject instead of site title" do
        SiteSetting.email_prefix = "Try Discourse"
        SiteSetting.title = "Discourse Meta"

        expect(email.subject).to match(/Try Discourse/)
        expect(email.subject).not_to match(/Discourse Meta/)
      end

      it "includes unread likes received count within the since date" do
        Fabricate(
          :notification,
          user: user,
          notification_type: Notification.types[:liked],
          created_at: 2.months.ago,
        )
        Fabricate(
          :notification,
          user: user,
          notification_type: Notification.types[:liked],
          read: true,
        )
        Fabricate(:notification, user: user, notification_type: Notification.types[:liked])
        Fabricate(:notification, user: user, notification_type: Notification.types[:liked])
        digest = UserNotifications.digest(user, since: 1.month.ago.to_date.to_s)
        parsed_html = Nokogiri::HTML5.fragment(digest.html_part.body.to_s)
        expect(parsed_html.css(".header-stat-count #likes_received_stat_count strong").text).to eq(
          "2",
        )
        expect(
          parsed_html.css(".header-stat-description #likes_received_stat_description strong").text,
        ).to eq("Likes Received")
      end

      it "excludes deleted topics and their posts" do
        deleted =
          Fabricate(
            :topic,
            user: Fabricate(:user),
            title: "Delete this topic plz",
            created_at: 1.hour.ago,
          )
        post =
          Fabricate(
            :post,
            topic: deleted,
            score: 100.0,
            post_number: 2,
            raw: "Your wish is my command",
            created_at: 1.hour.ago,
          )
        deleted.trash!
        html = email.html_part.body.to_s
        expect(html).to_not include deleted.title
        expect(html).to_not include post.raw
      end

      it "excludes shared drafts" do
        cat = Fabricate(:category)
        SiteSetting.shared_drafts_category = cat.id
        topic =
          Fabricate(:topic, title: "This is a draft", category_id: cat.id, created_at: 1.hour.ago)
        post =
          Fabricate(
            :post,
            topic: topic,
            score: 100.0,
            post_number: 2,
            raw: "secret draft content",
            created_at: 1.hour.ago,
          )
        html = email.html_part.body.to_s
        expect(html).to_not include topic.title
        expect(html).to_not include post.raw
      end

      it "excludes whispers and other post types that don't belong" do
        t =
          Fabricate(
            :topic,
            user: Fabricate(:user),
            title: "Who likes the same stuff I like?",
            created_at: 1.hour.ago,
          )
        whisper =
          Fabricate(
            :post,
            topic: t,
            score: 100.0,
            post_number: 2,
            raw: "You like weird stuff",
            post_type: Post.types[:whisper],
            created_at: 1.hour.ago,
          )
        mod_action =
          Fabricate(
            :post,
            topic: t,
            score: 100.0,
            post_number: 3,
            raw: "This topic unlisted",
            post_type: Post.types[:moderator_action],
            created_at: 1.hour.ago,
          )
        small_action =
          Fabricate(
            :post,
            topic: t,
            score: 100.0,
            post_number: 4,
            raw: "A small action",
            post_type: Post.types[:small_action],
            created_at: 1.hour.ago,
          )
        html = email.html_part.body.to_s
        expect(html).to_not include whisper.raw
        expect(html).to_not include mod_action.raw
        expect(html).to_not include small_action.raw
      end

      it "excludes deleted and hidden posts" do
        t =
          Fabricate(
            :topic,
            user: Fabricate(:user),
            title: "Post objectionable stuff here",
            created_at: 1.hour.ago,
          )
        deleted =
          Fabricate(
            :post,
            topic: t,
            score: 100.0,
            post_number: 2,
            raw: "This post is uncalled for",
            deleted_at: 5.minutes.ago,
            created_at: 1.hour.ago,
          )
        hidden =
          Fabricate(
            :post,
            topic: t,
            score: 100.0,
            post_number: 3,
            raw: "Try to find this post",
            hidden: true,
            hidden_at: 5.minutes.ago,
            hidden_reason_id: Post.hidden_reasons[:flagged_by_tl3_user],
            created_at: 1.hour.ago,
          )
        user_deleted =
          Fabricate(
            :post,
            topic: t,
            score: 100.0,
            post_number: 4,
            raw: "I regret this post",
            user_deleted: true,
            created_at: 1.hour.ago,
          )
        html = email.html_part.body.to_s
        expect(html).to_not include deleted.raw
        expect(html).to_not include hidden.raw
        expect(html).to_not include user_deleted.raw
      end

      it "excludes posts that are newer than editing grace period" do
        SiteSetting.editing_grace_period = 5.minutes
        too_new =
          Fabricate(
            :topic,
            user: Fabricate(:user),
            title: "Oops I need to edit this",
            created_at: 1.minute.ago,
          )
        _too_new_post =
          Fabricate(
            :post,
            user: too_new.user,
            topic: too_new,
            score: 100.0,
            post_number: 1,
            created_at: 1.minute.ago,
          )
        html = email.html_part.body.to_s
        expect(html).to_not include too_new.title
      end

      it "uses theme color" do
        cs =
          Fabricate(
            :color_scheme,
            name: "Fancy",
            color_scheme_colors: [
              Fabricate(:color_scheme_color, name: "header_primary", hex: "F0F0F0"),
              Fabricate(:color_scheme_color, name: "header_background", hex: "1E1E1E"),
            ],
          )
        theme =
          Fabricate(:theme, user_selectable: true, user: Fabricate(:admin), color_scheme_id: cs.id)

        theme.set_default!

        html = email.html_part.body.to_s
        expect(html).to include "F0F0F0"
        expect(html).to include "1E1E1E"
      end

      it "supports subfolder" do
        set_subfolder "/forum"
        html = email.html_part.body.to_s
        text = email.text_part.body.to_s
        expect(html).to be_present
        expect(text).to be_present
        expect(html).to_not include("/forum/forum")
        expect(text).to_not include("/forum/forum")
        expect(email.header["List-Unsubscribe"].to_s).to match(
          /http:\/\/test.localhost\/forum\/email\/unsubscribe\/\h{64}/,
        )

        topic_url = "http://test.localhost/forum/t/#{popular_topic.slug}/#{popular_topic.id}"
        expect(html).to include(topic_url)
        expect(text).to include(topic_url)
      end

      it "applies lang/xml:lang html attributes" do
        SiteSetting.default_locale = "pl_PL"
        html = email.html_part.to_s

        expect(html).to match(' lang="pl-PL"')
        expect(html).to match(' xml:lang="pl-PL"')
      end
    end
  end

  describe ".user_replied" do
    let(:response_by_user) { Fabricate(:user, name: "John Doe") }
    let(:category) { Fabricate(:category, name: "India") }
    let(:tag1) { Fabricate(:tag, name: "Taggo", public_topic_count: 1) }
    let(:tag2) { Fabricate(:tag, name: "Taggie", public_topic_count: 3) }
    let(:tag3) { Fabricate(:tag, name: "Teggo", public_topic_count: 2) }

    let(:hidden_tag) { Fabricate(:tag, name: "hidden") }
    let!(:hidden_tag_group) do
      Fabricate(:tag_group, permissions: { "staff" => 1 }, tag_names: [hidden_tag.name])
    end

    let(:topic) do
      Fabricate(
        :topic,
        category: category,
        tags: [tag1, tag2, tag3, hidden_tag],
        title: "Super cool topic",
      )
    end
    let(:post) { Fabricate(:post, topic: topic, raw: "This is My super duper cool topic") }
    let(:response) { Fabricate(:basic_reply, topic: post.topic, user: response_by_user) }
    let(:user) { Fabricate(:user) }
    let(:notification) { Fabricate(:replied_notification, user: user, post: response) }

    it "generates a correct email" do
      SiteSetting.default_email_in_reply_to = true

      # Fabricator is not fabricating this ...
      SiteSetting.email_subject =
        "[%{site_name}] %{optional_pm}%{optional_cat}%{optional_tags}%{topic_title}"
      SiteSetting.enable_names = true
      SiteSetting.display_name_on_posts = true
      mail =
        UserNotifications.user_replied(
          user,
          post: response,
          notification_type: notification.notification_type,
          notification_data_hash: notification.data_hash,
        )

      # from should include full user name
      expect(mail[:from].display_names).to eql(["John Doe via Discourse"])

      # subject should include category name
      expect(mail.subject).to match(/India/)

      # subject should include tag names
      expect(mail.subject).to match(/Taggo/)
      expect(mail.subject).to match(/Taggie/)

      mail_html = mail.html_part.body.to_s

      expect(mail_html.scan(/My super duper cool topic/).count).to eq(1)
      expect(mail_html.scan(/In Reply To/).count).to eq(1)

      # 2 "visit topic" link
      expect(mail_html.scan(/Visit Topic/).count).to eq(2)

      # 2 respond to links cause we have 1 context post
      expect(mail_html.scan(/to respond/).count).to eq(2)

      # 1 unsubscribe
      expect(mail_html.scan(/To unsubscribe/).count).to eq(1)

      # side effect, topic user is updated with post number
      tu = TopicUser.get(post.topic_id, user)
      expect(tu.last_emailed_post_number).to eq(response.post_number)

      # no In Reply To if user opts out
      user.user_option.email_in_reply_to = false
      mail =
        UserNotifications.user_replied(
          user,
          post: response,
          notification_type: notification.notification_type,
          notification_data_hash: notification.data_hash,
        )

      expect(mail.html_part.body.to_s.scan(/In Reply To/).count).to eq(0)

      SiteSetting.enable_names = true
      SiteSetting.display_name_on_posts = true
      SiteSetting.prioritize_username_in_ux = false

      response.user.username = "bobmarley"
      response.user.name = "Bob Marley"
      response.user.save

      mail =
        UserNotifications.user_replied(
          user,
          post: response,
          notification_type: notification.notification_type,
          notification_data_hash: notification.data_hash,
        )

      mail_html = mail.html_part.body.to_s
      expect(mail_html.scan(/>Bob Marley/).count).to eq(1)
      expect(mail_html.scan(/>bobmarley/).count).to eq(0)

      expect(mail.subject.scan(/#{tag1.name}/).count).to eq(1)
      expect(mail.subject.scan(/#{hidden_tag.name}/).count).to eq(0)

      SiteSetting.prioritize_username_in_ux = true

      mail =
        UserNotifications.user_replied(
          user,
          post: response,
          notification_type: notification.notification_type,
          notification_data_hash: notification.data_hash,
        )

      mail_html = mail.html_part.body.to_s
      expect(mail_html.scan(/>Bob Marley/).count).to eq(0)
      expect(mail_html.scan(/>bobmarley/).count).to eq(1)
    end

    describe "number of tags shown in subject line" do
      describe "max_tags_per_email_subject siteSetting enabled" do
        before { SiteSetting.enable_max_tags_per_email_subject = true }

        it "should match max_tags_per_email_subject" do
          SiteSetting.email_subject =
            "[%{site_name}] %{optional_pm}%{optional_cat}%{optional_tags}%{topic_title}"
          SiteSetting.max_tags_per_topic = 1
          SiteSetting.max_tags_per_email_subject = 2

          mail =
            UserNotifications.user_replied(
              user,
              post: response,
              notification_type: notification.notification_type,
              notification_data_hash: notification.data_hash,
            )

          expect(mail.subject).to eq(
            "[Discourse] [#{category.name}] #{tag2.name} #{tag3.name} #{topic.title}",
          )
        end
      end

      describe "max_tags_per_email_subject siteSetting disabled" do
        before { SiteSetting.enable_max_tags_per_email_subject = false }

        it "should match max_tags_per_topic" do
          SiteSetting.email_subject =
            "[%{site_name}] %{optional_pm}%{optional_cat}%{optional_tags}%{topic_title}"
          SiteSetting.max_tags_per_topic = 2
          SiteSetting.max_tags_per_email_subject = 1

          mail =
            UserNotifications.user_replied(
              user,
              post: response,
              notification_type: notification.notification_type,
              notification_data_hash: notification.data_hash,
            )

          expect(mail.subject).to eq(
            "[Discourse] [#{category.name}] #{tag2.name} #{tag3.name} #{topic.title}",
          )
        end
      end
    end

    it "doesn't include details when private_email is enabled" do
      SiteSetting.private_email = true
      mail =
        UserNotifications.user_replied(
          user,
          post: response,
          notification_type: notification.notification_type,
          notification_data_hash: notification.data_hash,
        )

      expect(mail.html_part.body.to_s).to_not include(response.raw)
      expect(mail.html_part.body.to_s).to_not include(topic.url)
      expect(mail.text_part.to_s).to_not include(response.raw)
      expect(mail.text_part.to_s).to_not include(topic.url)
    end

    it "includes excerpt when post_excerpts_in_emails is enabled" do
      paragraphs = [
        "This is the first paragraph, but you should read more.",
        "And here is its friend, the second paragraph.",
      ]
      SiteSetting.post_excerpts_in_emails = true
      SiteSetting.post_excerpt_maxlength = paragraphs.first.length
      response.update!(raw: paragraphs.join("\n\n"))
      mail =
        UserNotifications.user_replied(
          user,
          post: response,
          notification_type: notification.notification_type,
          notification_data_hash: notification.data_hash,
        )
      mail_html = mail.html_part.body.to_s
      expect(mail_html.scan(/#{paragraphs[0]}/).count).to eq(1)
      expect(mail_html.scan(/#{paragraphs[1]}/).count).to eq(0)
    end
  end

  describe ".user_posted" do
    let(:response_by_user) { Fabricate(:user, name: "John Doe", username: "john") }
    let(:topic) { Fabricate(:topic, title: "Super cool topic") }
    let(:post) { Fabricate(:post, topic: topic) }
    let(:response) { Fabricate(:post, topic: topic, user: response_by_user) }
    let(:user) { Fabricate(:user) }
    let(:notification) { Fabricate(:posted_notification, user: user, post: response) }

    it "generates a correct email" do
      SiteSetting.enable_names = false
      mail =
        UserNotifications.user_posted(
          user,
          post: response,
          notification_type: notification.notification_type,
          notification_data_hash: notification.data_hash,
        )

      # from should not include full user name if "show user full names" is disabled
      expect(mail[:from].display_names).to_not eql(["John Doe"])

      # from should include username if "show user full names" is disabled
      expect(mail[:from].display_names).to eql(["john via Discourse"])

      # subject should not include category name
      expect(mail.subject).not_to match(/Uncategorized/)

      # 1 respond to links as no context by default
      expect(mail.html_part.body.to_s.scan(/to respond/).count).to eq(1)

      # 1 unsubscribe link
      expect(mail.html_part.body.to_s.scan(/To unsubscribe/).count).to eq(1)

      # side effect, topic user is updated with post number
      tu = TopicUser.get(post.topic_id, user)
      expect(tu.last_emailed_post_number).to eq(response.post_number)
    end

    it "doesn't include details when private_email is enabled" do
      SiteSetting.private_email = true
      mail =
        UserNotifications.user_posted(
          user,
          post: response,
          notification_type: notification.notification_type,
          notification_data_hash: notification.data_hash,
        )

      expect(mail.html_part.body.to_s).to_not include(response.raw)
      expect(mail.text_part.to_s).to_not include(response.raw)
    end

    it "uses the original subject for staged users" do
      incoming_email =
        Fabricate(
          :incoming_email,
          subject: "Original Subject",
          post: post,
          topic: post.topic,
          user: user,
        )

      mail =
        UserNotifications.user_posted(
          user,
          post: response,
          notification_type: notification.notification_type,
          notification_data_hash: notification.data_hash,
        )
      expect(mail.subject).to match(/Super cool topic/)

      user.update!(staged: true)
      mail =
        UserNotifications.user_posted(
          user,
          post: response,
          notification_type: notification.notification_type,
          notification_data_hash: notification.data_hash,
        )
      expect(mail.subject).to eq("Re: Original Subject")

      another_post = Fabricate(:post, topic: topic)
      incoming_email.update!(post_id: another_post.id)

      mail =
        UserNotifications.user_private_message(
          user,
          post: response,
          notification_type: notification.notification_type,
          notification_data_hash: notification.data_hash,
        )
      expect(mail.subject).to match(/Super cool topic/)
    end
  end

  describe ".user_private_message" do
    let(:response_by_user) { Fabricate(:user, name: "", username: "john") }
    let(:topic) { Fabricate(:private_message_topic, title: "Super cool topic") }
    let(:post) { Fabricate(:post, topic: topic) }
    let(:response) { Fabricate(:post, topic: topic, user: response_by_user) }
    let(:user) { Fabricate(:user) }
    let(:notification) { Fabricate(:private_message_notification, user: user, post: response) }

    it "generates a correct email" do
      SiteSetting.enable_names = true
      mail =
        UserNotifications.user_private_message(
          user,
          post: response,
          notification_type: notification.notification_type,
          notification_data_hash: notification.data_hash,
        )

      # from should include username if full user name is not provided
      expect(mail[:from].display_names).to eql(["john via Discourse"])

      # subject should include "[PM]"
      expect(mail.subject).to include("[PM] ")

      # 1 "visit message" link
      expect(mail.html_part.body.to_s.scan(/Visit Message/).count).to eq(1)

      # 1 respond to link
      expect(mail.html_part.body.to_s.scan(/to respond/).count).to eq(1)

      # 1 unsubscribe link
      expect(mail.html_part.body.to_s.scan(/To unsubscribe/).count).to eq(1)

      # side effect, topic user is updated with post number
      tu = TopicUser.get(topic.id, user)
      expect(tu.last_emailed_post_number).to eq(response.post_number)
    end

    it "doesn't include details when private_email is enabled" do
      SiteSetting.private_email = true
      mail =
        UserNotifications.user_private_message(
          user,
          post: response,
          notification_type: notification.notification_type,
          notification_data_hash: notification.data_hash,
        )

      expect(mail.html_part.body.to_s).to_not include(response.raw)
      expect(mail.html_part.body.to_s).to_not include(topic.url)
      expect(mail.text_part.to_s).to_not include(response.raw)
      expect(mail.text_part.to_s).to_not include(topic.url)
    end

    it "doesn't include group name in subject" do
      group = Fabricate(:group)
      topic.allowed_groups = [group]
      mail =
        UserNotifications.user_private_message(
          user,
          post: response,
          notification_type: notification.notification_type,
          notification_data_hash: notification.data_hash,
        )

      expect(mail.subject).to include("[PM] ")
    end

    it "includes a list of participants (except for the destination user), groups first with member lists" do
      group1 = Fabricate(:group, name: "group1")
      group2 = Fabricate(:group, name: "group2")

      user1 = Fabricate(:user, username: "one", groups: [group1, group2])
      user2 = Fabricate(:user, username: "two", groups: [group1], staged: true)

      topic.allowed_users = [user, user1, user2]
      topic.allowed_groups = [group1, group2]

      mail =
        UserNotifications.user_private_message(
          user,
          post: response,
          notification_type: notification.notification_type,
          notification_data_hash: notification.data_hash,
        )

      expect(mail.body).to include(
        "[group1 (2)](http://test.localhost/g/group1), [group2 (1)](http://test.localhost/g/group2), [one](http://test.localhost/u/one), [two](http://test.localhost/u/two)",
      )
    end

    context "when SiteSetting.group_name_in_subject is true" do
      before { SiteSetting.group_in_subject = true }

      let(:group) { Fabricate(:group, name: "my_group") }
      let(:mail) do
        UserNotifications.user_private_message(
          user,
          post: response,
          notification_type: notification.notification_type,
          notification_data_hash: notification.data_hash,
        )
      end

      shared_examples "includes first group name" do
        it "includes first group name in subject" do
          expect(mail.subject).to include("[my_group] ")
        end

        context "when first group has full name" do
          it "includes full name in subject" do
            group.full_name = "My Group"
            group.save
            expect(mail.subject).to include("[My Group] ")
          end
        end
      end

      context "with one group in pm" do
        before { topic.allowed_groups = [group] }

        include_examples "includes first group name"
      end

      context "with multiple groups in pm" do
        let(:group2) { Fabricate(:group) }

        before { topic.allowed_groups = [group, group2] }

        include_examples "includes first group name"
      end

      context "with no groups in pm" do
        it "includes %{optional_pm} in subject" do
          expect(mail.subject).to include("[PM] ")
        end
      end
    end

    it "uses the original subject for staged users when topic was started via email" do
      incoming_email =
        Fabricate(
          :incoming_email,
          subject: "Original Subject",
          post: post,
          topic: topic,
          user: user,
        )

      mail =
        UserNotifications.user_private_message(
          user,
          post: response,
          notification_type: notification.notification_type,
          notification_data_hash: notification.data_hash,
        )
      expect(mail.subject).to match(/Super cool topic/)

      user.update!(staged: true)
      mail =
        UserNotifications.user_private_message(
          user,
          post: response,
          notification_type: notification.notification_type,
          notification_data_hash: notification.data_hash,
        )
      expect(mail.subject).to eq("Re: Original Subject")

      another_post = Fabricate(:post, topic: topic)
      incoming_email.update!(post_id: another_post.id)

      mail =
        UserNotifications.user_private_message(
          user,
          post: response,
          notification_type: notification.notification_type,
          notification_data_hash: notification.data_hash,
        )
      expect(mail.subject).to match(/Super cool topic/)
    end
  end

  it "adds a warning when mail limit is reached" do
    SiteSetting.max_emails_per_day_per_user = 2
    user = Fabricate(:user)

    user.email_logs.create!(email_type: "blah", to_address: user.email, user_id: user.id)

    post = Fabricate(:post)
    reply = Fabricate(:post, topic_id: post.topic_id)

    notification =
      Fabricate(
        :notification,
        topic_id: post.topic_id,
        post_number: reply.post_number,
        user: post.user,
        data: { original_username: "bob" }.to_json,
      )

    mail =
      UserNotifications.user_replied(
        user,
        post: reply,
        notification_type: notification.notification_type,
        notification_data_hash: notification.data_hash,
      )

    # WARNING: you reached the limit of 100 email notifications per day. Further emails will be suppressed.
    # Consider watching less topics or disabling mailing list mode.
    expect(mail.html_part.body.to_s).to match(I18n.t("user_notifications.reached_limit", count: 2))
    expect(mail.body.to_s).to match(I18n.t("user_notifications.reached_limit", count: 2))
  end

  def expects_build_with(condition)
    UserNotifications.any_instance.expects(:build_email).with(user.email, condition)
    mailer =
      UserNotifications.public_send(
        mail_type,
        user,
        notification_type: Notification.types[notification.notification_type],
        notification_data_hash: notification.data_hash,
        post: notification.post,
      )

    mailer.message
  end

  shared_examples "supports reply by email" do
    context "with reply_by_email" do
      it "should have allow_reply_by_email set when that feature is enabled" do
        expects_build_with(has_entry(:allow_reply_by_email, true))
      end
    end
  end

  shared_examples "no reply by email" do
    context "with reply_by_email" do
      it "doesn't support reply by email" do
        expects_build_with(Not(has_entry(:allow_reply_by_email, true)))
      end
    end
  end

  shared_examples "respect for private_email" do
    context "with private_email" do
      it "doesn't support reply by email" do
        SiteSetting.private_email = true

        mailer =
          UserNotifications.public_send(
            mail_type,
            user,
            notification_type: Notification.types[notification.notification_type],
            notification_data_hash: notification.data_hash,
            post: notification.post,
          )
        message = mailer.message

        topic = notification.post.topic
        expect(message.html_part.body.to_s).not_to include(topic.title)
        expect(message.html_part.body.to_s).not_to include(topic.slug)
        expect(message.text_part.body.to_s).not_to include(topic.title)
        expect(message.text_part.body.to_s).not_to include(topic.slug)
      end
    end
  end

  # The parts of emails that are derived from templates are translated
  shared_examples "sets user locale" do
    context "with set locale for translating templates" do
      it "sets the locale" do
        expects_build_with(has_key(:locale))
      end
    end
  end

  shared_examples "notification email building" do
    let(:post) { Fabricate(:post, user: user) }
    let(:mail_type) { "user_#{notification_type}" }
    let(:mail_template) { "user_notifications.#{mail_type}" }
    let(:username) { "walterwhite" }
    let(:notification) do
      Fabricate(
        :notification,
        user: user,
        topic: post.topic,
        notification_type: Notification.types[notification_type],
        post_number: post.post_number,
        data: { original_username: username }.to_json,
      )
    end

    describe "email building" do
      it "has a username" do
        expects_build_with(has_entry(:username, username))
      end

      it "has a url" do
        expects_build_with(has_key(:url))
      end

      it "has a template" do
        expects_build_with(has_entry(:template, mail_template))
      end

      it "overrides the html part" do
        expects_build_with(has_key(:html_override))
      end

      it "has a message" do
        expects_build_with(has_key(:message))
      end

      it "has a context" do
        expects_build_with(has_key(:context))
      end

      it "has an unsubscribe link" do
        expects_build_with(has_key(:add_unsubscribe_link))
      end

      it "has an post_id" do
        expects_build_with(has_key(:post_id))
      end

      it "has an topic_id" do
        expects_build_with(has_key(:topic_id))
      end

      it "should have user name as from_alias" do
        SiteSetting.enable_names = true
        SiteSetting.display_name_on_posts = true
        expects_build_with(has_entry(:from_alias, "#{user.name} via Discourse"))
      end

      it "should not have user name as from_alias if display_name_on_posts is disabled" do
        SiteSetting.enable_names = false
        SiteSetting.display_name_on_posts = false
        expects_build_with(has_entry(:from_alias, "walterwhite via Discourse"))
      end

      it "should explain how to respond" do
        expects_build_with(Not(has_entry(:include_respond_instructions, false)))
      end

      it "should not explain how to respond if the user is suspended" do
        User.any_instance.stubs(:suspended?).returns(true)
        expects_build_with(has_entry(:include_respond_instructions, false))
      end

      context "when customized" do
        let(:custom_body) do
          body = +<<~BODY
            You are now officially notified.
            %{header_instructions}
            %{message} %{respond_instructions}
            %{topic_title_url_encoded}
            %{site_title_url_encoded}
          BODY

          body << "%{context}" if notification_type != :invited_to_topic
          body
        end

        before do
          TranslationOverride.upsert!(
            SiteSetting.default_locale,
            "#{mail_template}.text_body_template",
            custom_body,
          )
        end

        it "shouldn't use the default html_override" do
          expects_build_with(Not(has_key(:html_override)))
        end
      end
    end
  end

  describe "user mentioned email" do
    include_examples "notification email building" do
      let(:notification_type) { :mentioned }
      include_examples "respect for private_email"
      include_examples "supports reply by email"
      include_examples "sets user locale"
    end
  end

  describe "group mentioned email" do
    include_examples "notification email building" do
      let(:notification_type) { :group_mentioned }
      let(:post) { Fabricate(:private_message_post) }
      let(:user) { post.user }
      let(:mail_type) { "group_mentioned" }
      let(:mail_template) { "user_notifications.user_#{notification_type}_pm" }

      include_examples "respect for private_email"
      include_examples "supports reply by email"
      include_examples "sets user locale"
    end
  end

  describe "user replied" do
    include_examples "notification email building" do
      let(:notification_type) { :replied }
      include_examples "respect for private_email"
      include_examples "supports reply by email"
      include_examples "sets user locale"
    end
  end

  describe "user quoted" do
    include_examples "notification email building" do
      let(:notification_type) { :quoted }
      include_examples "respect for private_email"
      include_examples "supports reply by email"
      include_examples "sets user locale"
    end
  end

  describe "user posted" do
    include_examples "notification email building" do
      let(:notification_type) { :posted }
      include_examples "respect for private_email"
      include_examples "supports reply by email"
      include_examples "sets user locale"
    end
  end

  describe "user invited to a private message" do
    include_examples "notification email building" do
      let(:notification_type) { :invited_to_private_message }
      let(:post) { Fabricate(:private_message_post) }
      let(:user) { post.user }
      let(:mail_template) { "user_notifications.user_#{notification_type}_pm" }

      include_examples "respect for private_email"
      include_examples "no reply by email"
      include_examples "sets user locale"
    end
  end

  describe "group invited to a private message" do
    include_examples "notification email building" do
      let(:notification_type) { :invited_to_private_message }
      let(:post) { Fabricate(:private_message_post) }
      let(:user) { post.user }
      let(:group) { Fabricate(:group) }
      let(:mail_template) { "user_notifications.user_#{notification_type}_pm_group" }

      before do
        notification.data_hash[:group_id] = group.id
        notification.save!
      end

      it "should include the group name" do
        expects_build_with(has_entry(:group_name, group.name))
      end

      include_examples "respect for private_email"
      include_examples "no reply by email"
      include_examples "sets user locale"
    end
  end

  describe "user invited to a topic" do
    let(:notification_type) { :invited_to_topic }

    include_examples "notification email building" do
      include_examples "respect for private_email"
      include_examples "no reply by email"
      include_examples "sets user locale"
    end

    context "when showing the right name in 'From' field" do
      let(:inviter) { Fabricate(:user) }
      let(:invitee) { Fabricate(:user) }

      let(:notification) do
        Fabricate(
          :notification,
          notification_type: Notification.types[:invited_to_topic],
          user: invitee,
          topic: post.topic,
          post_number: post.post_number,
          data: {
            topic_title: post.topic.title,
            display_username: inviter.username,
            original_user_id: inviter.id,
            original_username: inviter.username,
          }.to_json,
        )
      end

      let(:mailer) do
        UserNotifications.public_send(
          "user_invited_to_topic",
          invitee,
          notification_type: Notification.types[notification.notification_type],
          notification_data_hash: notification.data_hash,
          post: notification.post,
        )
      end

      it "sends the email as the inviter" do
        SiteSetting.enable_names = false

        expect(mailer.message.to_s).to include(
          "From: #{inviter.username} via #{SiteSetting.title} <#{SiteSetting.notification_email}>",
        )
      end

      it "sends the email as the inviter" do
        expect(mailer.message.to_s).to include(
          "From: #{inviter.name} via #{SiteSetting.title} <#{SiteSetting.notification_email}>",
        )
      end
    end
  end

  describe "watching first post" do
    include_examples "notification email building" do
      let(:notification_type) { :invited_to_topic }
      include_examples "respect for private_email"
      include_examples "no reply by email"
      include_examples "sets user locale"
    end
  end

  # notification emails derived from templates are translated into the user's locale
  shared_context "with notification derived from template" do
    let(:user) { Fabricate(:user, locale: locale) }
    let(:mail_type) { mail_type }
    let(:notification) { Fabricate(:notification, user: user) }
  end

  describe "notifications from template" do
    context "when user locale is allowed" do
      before { SiteSetting.allow_user_locale = true }

      %w[
        signup
        signup_after_approval
        confirm_old_email
        notify_old_email
        confirm_new_email
        forgot_password
        admin_login
        account_created
      ].each do |mail_type|
        include_examples "with notification derived from template" do
          let(:locale) { "fr" }
          let(:mail_type) { mail_type }
          it "sets the locale" do
            expects_build_with(has_entry(:locale, "fr"))
          end
        end
      end
    end

    context "when user locale is not allowed" do
      before { SiteSetting.allow_user_locale = false }

      %w[
        signup
        signup_after_approval
        notify_old_email
        confirm_old_email
        confirm_new_email
        forgot_password
        admin_login
        account_created
      ].each do |mail_type|
        include_examples "with notification derived from template" do
          let(:locale) { "fr" }
          let(:mail_type) { mail_type }
          it "sets the locale" do
            expects_build_with(has_entry(:locale, "en"))
          end
        end
      end
    end
  end

  describe "#participants" do
    fab!(:group1) { Fabricate(:group, name: "group1") }
    fab!(:group2) { Fabricate(:group, name: "group2") }
    fab!(:group3) { Fabricate(:group, name: "group3") }
    fab!(:user1) { Fabricate(:user, username: "one", name: nil, groups: [group1, group2]) }
    fab!(:user2) { Fabricate(:user, username: "two", name: nil, groups: [group1]) }
    fab!(:user3) { Fabricate(:user, username: "three", name: nil, groups: [group3]) }
    fab!(:user4) { Fabricate(:user, username: "four", name: nil, groups: [group1, group3]) }
    fab!(:admin) { Fabricate(:admin, username: "admin", name: nil) }

    fab!(:topic) do
      t = Fabricate(:private_message_topic, title: "Super cool topic")
      t.allowed_users = [user1, user2, user3, user4, admin]
      t.allowed_groups = [group1]
      t
    end
    fab!(:posts) do
      [
        Fabricate(:post, topic: topic, post_number: 1, user: user2),
        Fabricate(:post, topic: topic, post_number: 2, user: user1),
        Fabricate(:post, topic: topic, post_number: 3, user: user2),
        Fabricate(:small_action, topic: topic, post_number: 4, user: admin),
        Fabricate(:post, topic: topic, post_number: 5, user: user4),
        Fabricate(:post, topic: topic, post_number: 6, user: user3),
        Fabricate(:post, topic: topic, post_number: 7, user: user4),
      ]
    end

    it "returns a list of participants (except for the recipient), groups first, followed by users in order of their last reply" do
      expect(UserNotifications.participants(posts.last, user3)).to eq(
        "[group1 (3)](http://test.localhost/g/group1), " \
          "[four](http://test.localhost/u/four), [two](http://test.localhost/u/two), [one](http://test.localhost/u/one), " \
          "[admin](http://test.localhost/u/admin)",
      )
    end

    it "caps the list according to site setting" do
      SiteSetting.max_participant_names = 3
      list =
        "[group1 (3)](http://test.localhost/g/group1), [four](http://test.localhost/u/four), [two](http://test.localhost/u/two)"
      expect(UserNotifications.participants(posts.last, user3)).to eq(
        I18n.t("user_notifications.more_pm_participants", participants: list, count: 2),
      )
    end

    it "orders groups by user count" do
      SiteSetting.max_participant_names = 3
      topic.allowed_groups = [group1, group2, group3]

      list =
        "[group1 (3)](http://test.localhost/g/group1), [group3 (2)](http://test.localhost/g/group3), [group2 (1)](http://test.localhost/g/group2)"
      expect(UserNotifications.participants(posts.last, user3)).to eq(
        I18n.t("user_notifications.more_pm_participants", participants: list, count: 4),
      )
    end

    it "orders users by their last reply and user id" do
      expect(UserNotifications.participants(posts[-3], user4)).to eq(
        "[group1 (3)](http://test.localhost/g/group1), " \
          "[two](http://test.localhost/u/two), [one](http://test.localhost/u/one), [three](http://test.localhost/u/three), " \
          "[admin](http://test.localhost/u/admin)",
      )
    end

    it "prefers full group names when available" do
      SiteSetting.max_participant_names = 2
      topic.allowed_groups = [group1, group2]

      group2.update!(full_name: "Awesome Group")

      list =
        "[group1 (3)](http://test.localhost/g/group1), [Awesome Group (1)](http://test.localhost/g/group2)"
      expect(UserNotifications.participants(posts.last, user3)).to eq(
        I18n.t("user_notifications.more_pm_participants", participants: list, count: 4),
      )
    end

    it "always uses usernames when prioritize_username_in_ux is enabled" do
      user4.update!(name: "James Bond")
      user1.update!(name: "Indiana Jones")

      SiteSetting.prioritize_username_in_ux = true
      expect(UserNotifications.participants(posts.last, user3)).to eq(
        "[group1 (3)](http://test.localhost/g/group1), " \
          "[four](http://test.localhost/u/four), [two](http://test.localhost/u/two), [one](http://test.localhost/u/one), " \
          "[admin](http://test.localhost/u/admin)",
      )

      SiteSetting.prioritize_username_in_ux = false
      expect(UserNotifications.participants(posts.last, user3)).to eq(
        "[group1 (3)](http://test.localhost/g/group1), " \
          "[James Bond](http://test.localhost/u/four), [two](http://test.localhost/u/two), [Indiana Jones](http://test.localhost/u/one), " \
          "[admin](http://test.localhost/u/admin)",
      )
    end

    it "reveals the email address of staged users if enabled" do
      user4.update!(staged: true, email: "james.bond@mi6.invalid")
      user1.update!(staged: true, email: "indiana.jones@example.com")

      SiteSetting.prioritize_username_in_ux = true
      expect(UserNotifications.participants(posts.last, user3, reveal_staged_email: true)).to eq(
        "[group1 (3)](http://test.localhost/g/group1), james.bond@mi6.invalid, [two](http://test.localhost/u/two), " \
          "indiana.jones@example.com, [admin](http://test.localhost/u/admin)",
      )
    end

    it "does only include human users" do
      topic.allowed_users << Discourse.system_user

      expect(UserNotifications.participants(posts.last, user3)).to eq(
        "[group1 (3)](http://test.localhost/g/group1), " \
          "[four](http://test.localhost/u/four), [two](http://test.localhost/u/two), [one](http://test.localhost/u/one), " \
          "[admin](http://test.localhost/u/admin)",
      )
    end
  end

  describe ".account_silenced" do
    fab!(:user_history) { Fabricate(:user_history, action: UserHistory.actions[:silence_user]) }

    it "adds the silenced_till date in user's timezone" do
      user.user_option.timezone = "Asia/Tbilisi" # GMT+4
      user.silenced_till = DateTime.parse("May 25, 2020, 12:00pm")

      mail = UserNotifications.account_silenced(user, { user_history: user_history })

      expect(mail.body).to include("May 25, 2020,  4:00pm")
    end

    context "when user doesn't have timezone set" do
      before { user.user_option.timezone = nil }

      it "doesn't raise error" do
        expect { UserNotifications.account_silenced(user) }.not_to raise_error
      end

      it "adds the silenced_till date in UTC" do
        date = "May 25, 2020, 12:00pm"
        user.silenced_till = DateTime.parse(date)

        mail = UserNotifications.account_silenced(user, { user_history: user_history })

        expect(mail.body).to include(date)
      end
    end

    context "when user timezone is invalid" do
      before { user.user_option.timezone = "" }

      it "doesn't raise error" do
        expect { UserNotifications.account_silenced(user) }.not_to raise_error
      end

      it "adds the silenced_till date in UTC" do
        date = "May 25, 2020, 12:00pm"
        user.silenced_till = DateTime.parse(date)

        mail = UserNotifications.account_silenced(user, { user_history: user_history })

        expect(mail.body).to include(date)
      end
    end
  end

  describe ".account_suspended" do
    fab!(:user_history) { Fabricate(:user_history, action: UserHistory.actions[:suspend_user]) }

    it "adds the suspended_till date in user's timezone" do
      user.user_option.timezone = "Asia/Tbilisi" # GMT+4
      user.suspended_till = DateTime.parse("May 25, 2020, 12:00pm")

      mail = UserNotifications.account_suspended(user, { user_history: user_history })

      expect(mail.body).to include("May 25, 2020,  4:00pm")
    end

    context "when user doesn't have timezone set" do
      before { user.user_option.timezone = nil }

      it "doesn't raise error" do
        expect { UserNotifications.account_suspended(user) }.not_to raise_error
      end

      it "adds the suspended_till date in UTC" do
        date = "May 25, 2020, 12:00pm"
        user.suspended_till = DateTime.parse(date)

        mail = UserNotifications.account_suspended(user, { user_history: user_history })

        expect(mail.body).to include(date)
      end
    end

    context "when user timezone is invalid" do
      before { user.user_option.timezone = "" }

      it "doesn't raise error" do
        expect { UserNotifications.account_suspended(user) }.not_to raise_error
      end

      it "adds the suspended_till date in UTC" do
        date = "May 25, 2020, 12:00pm"
        user.suspended_till = DateTime.parse(date)

        mail = UserNotifications.account_suspended(user, { user_history: user_history })

        expect(mail.body).to include(date)
      end
    end
  end
end