# frozen_string_literal: true

RSpec.describe TopicCreator do
  fab!(:user) { Fabricate(:user, trust_level: TrustLevel[2]) }
  fab!(:moderator)
  fab!(:admin)

  let(:valid_attrs) { Fabricate.attributes_for(:topic) }
  let(:pm_valid_attrs) do
    {
      raw: "this is a new post",
      title: "this is a new title",
      archetype: Archetype.private_message,
      target_usernames: moderator.username,
    }
  end

  let(:pm_to_email_valid_attrs) do
    {
      raw: "this is a new email",
      title: "this is a new subject",
      archetype: Archetype.private_message,
      target_emails: "moderator@example.com",
    }
  end

  describe "#create" do
    context "with topic success cases" do
      before do
        TopicCreator.any_instance.expects(:save_topic).returns(true)
        TopicCreator.any_instance.expects(:watch_topic).returns(true)
        SiteSetting.allow_duplicate_topic_titles = true
      end

      it "should be possible for an admin to create a topic" do
        expect(TopicCreator.create(admin, Guardian.new(admin), valid_attrs)).to be_valid
      end

      it "should be possible for a moderator to create a topic" do
        expect(TopicCreator.create(moderator, Guardian.new(moderator), valid_attrs)).to be_valid
      end

      it "supports custom_fields that has been registered to the DiscoursePluginRegistry" do
        opts = valid_attrs.merge(custom_fields: { import_id: "bar" })

        topic = TopicCreator.create(admin, Guardian.new(admin), opts)

        expect(topic.custom_fields["import_id"]).to eq("bar")
      end

      context "with regular user" do
        before { SiteSetting.min_trust_to_create_topic = TrustLevel[0] }

        it "should be possible for a regular user to create a topic" do
          expect(TopicCreator.create(user, Guardian.new(user), valid_attrs)).to be_valid
        end

        it "should be possible for a regular user to create a topic with blank auto_close_time" do
          expect(
            TopicCreator.create(user, Guardian.new(user), valid_attrs.merge(auto_close_time: "")),
          ).to be_valid
        end

        it "ignores auto_close_time without raising an error" do
          topic =
            TopicCreator.create(user, Guardian.new(user), valid_attrs.merge(auto_close_time: "24"))
          expect(topic).to be_valid
          expect(topic.public_topic_timer).to eq(nil)
        end

        it "can create a topic in a category" do
          category = Fabricate(:category, name: "Neil's Blog")
          topic =
            TopicCreator.create(user, Guardian.new(user), valid_attrs.merge(category: category.id))
          expect(topic).to be_valid
          expect(topic.category).to eq(category)
        end

        it "ignores participant_count without raising an error" do
          topic =
            TopicCreator.create(user, Guardian.new(user), valid_attrs.merge(participant_count: 3))
          expect(topic.participant_count).to eq(1)
        end

        it "accepts participant_count in import mode" do
          topic =
            TopicCreator.create(
              user,
              Guardian.new(user),
              valid_attrs.merge(import_mode: true, participant_count: 3),
            )
          expect(topic.participant_count).to eq(3)
        end
      end
    end

    context "with tags" do
      fab!(:tag1) { Fabricate(:tag, name: "fun") }
      fab!(:tag2) { Fabricate(:tag, name: "fun2") }
      fab!(:tag3) { Fabricate(:tag, name: "fun3") }
      fab!(:tag4) { Fabricate(:tag, name: "fun4") }
      fab!(:tag5) { Fabricate(:tag, name: "fun5") }
      fab!(:tag_group1) { Fabricate(:tag_group, tags: [tag1]) }
      fab!(:tag_group2) { Fabricate(:tag_group, tags: [tag2]) }

      before do
        SiteSetting.tagging_enabled = true
        SiteSetting.min_trust_to_create_tag = 0
        SiteSetting.min_trust_level_to_tag_topics = 0
      end

      context "with regular tags" do
        it "user can add tags to topic" do
          topic =
            TopicCreator.create(user, Guardian.new(user), valid_attrs.merge(tags: [tag1.name]))
          expect(topic).to be_valid
          expect(topic.tags.length).to eq(1)
        end
      end

      context "when assigned via matched watched words" do
        fab!(:word1) do
          Fabricate(:watched_word, action: WatchedWord.actions[:tag], replacement: tag1.name)
        end
        fab!(:word2) do
          Fabricate(:watched_word, action: WatchedWord.actions[:tag], replacement: tag2.name)
        end
        fab!(:word3) do
          Fabricate(
            :watched_word,
            action: WatchedWord.actions[:tag],
            replacement: tag3.name,
            case_sensitive: true,
          )
        end

        it "adds watched words as tags" do
          topic =
            TopicCreator.create(
              user,
              Guardian.new(user),
              valid_attrs.merge(
                title: "This is a #{word1.word} title",
                raw: "#{word2.word.upcase} is not the same as #{word3.word.upcase}",
              ),
            )

          expect(topic).to be_valid
          expect(topic.tags).to contain_exactly(tag1, tag2)
        end
      end

      context "with staff-only tags" do
        before { create_staff_only_tags(["alpha"]) }

        it "regular users can't add staff-only tags" do
          expect do
            TopicCreator.create(user, Guardian.new(user), valid_attrs.merge(tags: ["alpha"]))
          end.to raise_error(ActiveRecord::Rollback)
        end

        it "staff can add staff-only tags" do
          topic =
            TopicCreator.create(admin, Guardian.new(admin), valid_attrs.merge(tags: ["alpha"]))
          expect(topic).to be_valid
          expect(topic.tags.length).to eq(1)
        end
      end

      context "when minimum_required_tags is present" do
        fab!(:category) { Fabricate(:category, name: "beta", minimum_required_tags: 2) }

        it "fails for regular user if minimum_required_tags is not satisfied" do
          expect(
            TopicCreator.new(
              user,
              Guardian.new(user),
              valid_attrs.merge(category: category.id),
            ).valid?,
          ).to be_falsy
        end

        it "lets admin create a topic regardless of minimum_required_tags" do
          topic =
            TopicCreator.create(
              admin,
              Guardian.new(admin),
              valid_attrs.merge(tags: [tag1.name], category: category.id),
            )
          expect(topic).to be_valid
          expect(topic.tags.length).to eq(1)
        end

        it "works for regular user if minimum_required_tags is satisfied" do
          topic =
            TopicCreator.create(
              user,
              Guardian.new(user),
              valid_attrs.merge(tags: [tag1.name, tag2.name], category: category.id),
            )
          expect(topic).to be_valid
          expect(topic.tags.length).to eq(2)
        end

        it "minimum_required_tags is satisfying for new tags if user can create" do
          topic =
            TopicCreator.create(
              user,
              Guardian.new(user),
              valid_attrs.merge(tags: ["new tag", "another tag"], category: category.id),
            )
          expect(topic).to be_valid
          expect(topic.tags.length).to eq(2)
        end

        it "lets new user create a topic if they don't have sufficient trust level to tag topics" do
          SiteSetting.min_trust_level_to_tag_topics = 1
          new_user = Fabricate(:newuser)
          topic =
            TopicCreator.create(
              new_user,
              Guardian.new(new_user),
              valid_attrs.merge(category: category.id),
            )
          expect(topic).to be_valid
        end
      end

      context "with required tag group" do
        fab!(:tag_group) { Fabricate(:tag_group, tags: [tag1]) }
        fab!(:category) do
          Fabricate(
            :category,
            name: "beta",
            category_required_tag_groups: [
              CategoryRequiredTagGroup.new(tag_group: tag_group, min_count: 1),
            ],
          )
        end

        it "when no tags are not present" do
          expect(
            TopicCreator.new(
              user,
              Guardian.new(user),
              valid_attrs.merge(category: category.id),
            ).valid?,
          ).to be_falsy
        end

        it "when tags are not part of the tag group" do
          expect(
            TopicCreator.new(
              user,
              Guardian.new(user),
              valid_attrs.merge(category: category.id, tags: ["nope"]),
            ).valid?,
          ).to be_falsy
        end

        it "when requirement is met" do
          expect(
            TopicCreator.new(
              user,
              Guardian.new(user),
              valid_attrs.merge(category: category.id, tags: [tag1.name, tag2.name]),
            ).valid?,
          ).to be_truthy
        end

        it "lets staff ignore the restriction" do
          expect(
            TopicCreator.new(
              user,
              Guardian.new(admin),
              valid_attrs.merge(category: category.id),
            ).valid?,
          ).to be_truthy
        end
      end

      context "when category has restricted tags or tag groups" do
        fab!(:category) { Fabricate(:category, tags: [tag3], tag_groups: [tag_group1]) }

        it "allows topics without any tags" do
          tc =
            TopicCreator.new(
              user,
              Guardian.new(user),
              title: "hello this is a test topic without tags",
              raw: "hello this is a test topic without tags",
              category: category.id,
            )
          expect(tc.valid?).to eq(true)
          expect(tc.errors).to be_empty
          topic = tc.create
          expect(topic.tags).to be_empty
        end

        it "allows topics if they use tags only from the tags set that the category restricts" do
          tc =
            TopicCreator.new(
              user,
              Guardian.new(user),
              title: "hello this is a test topic with tags",
              raw: "hello this is a test topic with tags",
              category: category.id,
              tags: [tag1.name, tag3.name],
            )
          expect(tc.valid?).to eq(true)
          expect(tc.errors).to be_empty
          topic = tc.create
          expect(topic.tags).to contain_exactly(tag1, tag3)
        end

        it "allows topics to use tags that are restricted in multiple categories" do
          category2 = Fabricate(:category, tags: [tag5], tag_groups: [tag_group1])
          tc =
            TopicCreator.new(
              user,
              Guardian.new(user),
              title: "hello this is a test topic with tags",
              raw: "hello this is a test topic with tags",
              category: category2.id,
              tags: [tag1.name, tag5.name],
            )
          expect(tc.valid?).to eq(true)
          expect(tc.errors).to be_empty
          topic = tc.create
          expect(topic.tags).to contain_exactly(tag1, tag5)

          tc =
            TopicCreator.new(
              user,
              Guardian.new(user),
              title: "hello this is a test topic with tags 1",
              raw: "hello this is a test topic with tags",
              category: category.id,
              tags: [tag1.name, tag3.name],
            )
          expect(tc.valid?).to eq(true)
          expect(tc.errors).to be_empty
          topic = tc.create
          expect(topic.tags).to contain_exactly(tag1, tag3)

          tc =
            TopicCreator.new(
              user,
              Guardian.new(user),
              title: "hello this is a test topic with tags 2",
              raw: "hello this is a test topic with tags",
              category: category.id,
              tags: [tag1.name, tag5.name],
            )
          expect(tc.valid?).to eq(false)
          expect(tc.errors.full_messages).to contain_exactly(
            I18n.t(
              "tags.forbidden.restricted_tags_cannot_be_used_in_category",
              count: 1,
              tags: tag5.name,
              category: category.name,
            ),
          )
        end

        it "rejects topics if they use a tag outside the set of tags that the category restricts" do
          tc =
            TopicCreator.new(
              user,
              Guardian.new(user),
              title: "hello this is a test topic with tags",
              raw: "hello this is a test topic with tags",
              category: category.id,
              tags: [tag2.name, tag1.name],
            )
          expect(tc.valid?).to eq(false)
          expect(tc.errors.full_messages).to contain_exactly(
            I18n.t(
              "tags.forbidden.category_does_not_allow_tags",
              count: 1,
              tags: tag2.name,
              category: category.name,
            ),
          )

          tc =
            TopicCreator.new(
              user,
              Guardian.new(user),
              title: "hello this is a test topic with tags",
              raw: "hello this is a test topic with tags",
              category: category.id,
              tags: [tag2.name, tag5.name, tag3.name],
            )
          expect(tc.valid?).to eq(false)
          expect(tc.errors.full_messages).to contain_exactly(
            I18n.t(
              "tags.forbidden.category_does_not_allow_tags",
              count: 2,
              tags: [tag2, tag5].map(&:name).sort.join(", "),
              category: category.name,
            ),
          )
        end

        it "rejects topics in other categories if a restricted tag of a category are used" do
          category2 = Fabricate(:category)
          tc =
            TopicCreator.new(
              user,
              Guardian.new(user),
              title: "hello this is a test topic with tags",
              raw: "hello this is a test topic with tags",
              category: category2.id,
              tags: [tag1.name, tag2.name],
            )
          expect(tc.valid?).to eq(false)
          expect(tc.errors.full_messages).to contain_exactly(
            I18n.t(
              "tags.forbidden.restricted_tags_cannot_be_used_in_category",
              count: 1,
              tags: tag1.name,
              category: category2.name,
            ),
          )
        end

        context "when allowing other tags" do
          before { category.update!(allow_global_tags: true) }

          it "allows topics to use tags that aren't restricted by any category" do
            tc =
              TopicCreator.new(
                user,
                Guardian.new(user),
                title: "hello this is a test topic with tags",
                raw: "hello this is a test topic with tags",
                category: category.id,
                tags: [tag1.name, tag2.name, tag3.name, tag5.name],
              )
            expect(tc.valid?).to eq(true)
            expect(tc.errors).to be_empty
            topic = tc.create
            expect(topic.tags).to contain_exactly(tag1, tag2, tag3, tag5)
          end

          it "rejects topics if they use restricted tags of another category" do
            Fabricate(:category, tags: [tag5], tag_groups: [tag_group2])
            tc =
              TopicCreator.new(
                user,
                Guardian.new(user),
                title: "hello this is a test topic with tags",
                raw: "hello this is a test topic with tags",
                category: category.id,
                tags: [tag1.name, tag5.name],
              )
            expect(tc.valid?).to eq(false)
            expect(tc.errors.full_messages).to contain_exactly(
              I18n.t(
                "tags.forbidden.restricted_tags_cannot_be_used_in_category",
                count: 1,
                tags: tag5.name,
                category: category.name,
              ),
            )

            tc =
              TopicCreator.new(
                user,
                Guardian.new(user),
                title: "hello this is a test topic with tags",
                raw: "hello this is a test topic with tags",
                category: category.id,
                tags: [tag1.name, tag2.name, tag5.name],
              )
            expect(tc.valid?).to eq(false)
            expect(tc.errors.full_messages).to contain_exactly(
              I18n.t(
                "tags.forbidden.restricted_tags_cannot_be_used_in_category",
                count: 2,
                tags: [tag2, tag5].map(&:name).sort.join(", "),
                category: category.name,
              ),
            )
          end
        end
      end
    end

    context "with personal message" do
      context "with success cases" do
        before do
          TopicCreator.any_instance.expects(:save_topic).returns(true)
          TopicCreator.any_instance.expects(:watch_topic).returns(true)
          SiteSetting.allow_duplicate_topic_titles = true
          SiteSetting.enable_staged_users = true
          Group.refresh_automatic_groups!
        end

        it "should be possible for a regular user to send private message" do
          expect(TopicCreator.create(user, Guardian.new(user), pm_valid_attrs)).to be_valid
        end

        it "min_trust_to_create_topic setting should not be checked when sending private message" do
          SiteSetting.min_trust_to_create_topic = TrustLevel[4]
          expect(TopicCreator.create(user, Guardian.new(user), pm_valid_attrs)).to be_valid
        end

        it "personal_message_enabled_groups setting should not be checked when sending private messages to staff via flag" do
          SiteSetting.personal_message_enabled_groups = Group::AUTO_GROUPS[:staff]
          expect(
            TopicCreator.create(
              user,
              Guardian.new(user),
              pm_valid_attrs.merge(subtype: TopicSubtype.notify_moderators),
            ),
          ).to be_valid
        end
      end

      context "with failure cases" do
        it "should be rollback the changes when email is invalid" do
          SiteSetting.manual_polling_enabled = true
          SiteSetting.reply_by_email_address = "sam+%{reply_key}@sam.com"
          SiteSetting.reply_by_email_enabled = true
          SiteSetting.min_trust_to_send_email_messages = TrustLevel[1]
          attrs = pm_to_email_valid_attrs.dup
          attrs[:target_emails] = "t" * 256

          expect do TopicCreator.create(user, Guardian.new(user), attrs) end.to raise_error(
            ActiveRecord::Rollback,
          )
        end

        it "personal_message_enabled_groups setting should be checked when sending private message" do
          SiteSetting.personal_message_enabled_groups = Group::AUTO_GROUPS[:trust_level_4]

          expect do
            TopicCreator.create(user, Guardian.new(user), pm_valid_attrs)
          end.to raise_error(ActiveRecord::Rollback)
        end
      end

      context "with to emails" do
        before { Group.refresh_automatic_groups! }

        it "works for staff" do
          SiteSetting.min_trust_to_send_email_messages = "staff"
          expect(
            TopicCreator.create(admin, Guardian.new(admin), pm_to_email_valid_attrs),
          ).to be_valid
        end

        it "work for trusted users" do
          SiteSetting.min_trust_to_send_email_messages = 3
          user.update!(trust_level: 3)
          expect(TopicCreator.create(user, Guardian.new(user), pm_to_email_valid_attrs)).to be_valid
        end

        it "does not work for non-staff" do
          SiteSetting.min_trust_to_send_email_messages = "staff"
          expect {
            TopicCreator.create(user, Guardian.new(user), pm_to_email_valid_attrs)
          }.to raise_error(ActiveRecord::Rollback)
        end

        it "does not work for untrusted users" do
          SiteSetting.min_trust_to_send_email_messages = 3
          user.update!(trust_level: 2)
          expect {
            TopicCreator.create(user, Guardian.new(user), pm_to_email_valid_attrs)
          }.to raise_error(ActiveRecord::Rollback)
        end
      end
    end

    context "when setting timestamps" do
      it "supports Time instances" do
        freeze_time

        topic =
          TopicCreator.create(
            user,
            Guardian.new(user),
            valid_attrs.merge(created_at: 1.week.ago, pinned_at: 3.days.ago),
          )

        expect(topic.created_at).to eq_time(1.week.ago)
        expect(topic.pinned_at).to eq_time(3.days.ago)
      end

      it "supports strings" do
        freeze_time

        time1 = Time.zone.parse("2019-09-02")
        time2 = Time.zone.parse("2020-03-10 15:17")

        topic =
          TopicCreator.create(
            user,
            Guardian.new(user),
            valid_attrs.merge(created_at: "2019-09-02", pinned_at: "2020-03-10 15:17"),
          )

        expect(topic.created_at).to eq_time(time1)
        expect(topic.pinned_at).to eq_time(time2)
      end
    end

    context "with external_id" do
      it "adds external_id" do
        topic =
          TopicCreator.create(
            user,
            Guardian.new(user),
            valid_attrs.merge(external_id: "external_id"),
          )

        expect(topic.external_id).to eq("external_id")
      end
    end

    context "when invisible/unlisted" do
      let(:unlisted_attrs) { valid_attrs.merge(visible: false) }

      it "throws an exception for a non-staff user" do
        expect do TopicCreator.create(user, Guardian.new(user), unlisted_attrs) end.to raise_error(
          ActiveRecord::Rollback,
        )
      end

      it "is invalid for a non-staff user" do
        expect(TopicCreator.new(user, Guardian.new(user), unlisted_attrs).valid?).to eq(false)
      end

      it "creates unlisted topic for an admin" do
        expect(TopicCreator.create(admin, Guardian.new(admin), unlisted_attrs)).to be_valid
      end

      it "is valid for an admin" do
        expect(TopicCreator.new(admin, Guardian.new(admin), unlisted_attrs).valid?).to eq(true)
      end
    end
  end
end