# frozen_string_literal: true

RSpec.describe Draft do
  fab!(:user)

  fab!(:post)

  it { is_expected.to have_many(:upload_references).dependent(:delete_all) }

  describe "system user" do
    it "can not set drafts" do
      # fake a sequence
      DraftSequence.create!(user_id: Discourse.system_user.id, draft_key: "abc", sequence: 10)

      seq = Draft.set(Discourse.system_user, "abc", 0, { reply: "hi" }.to_json)
      expect(seq).to eq(0)

      draft = Draft.get(Discourse.system_user, "abc", 0)
      expect(draft).to eq(nil)

      draft = Draft.get(Discourse.system_user, "abc", 1)
      expect(draft).to eq(nil)
    end
  end

  describe "backup_drafts_to_pm_length" do
    it "correctly backs up drafts to a personal message" do
      SiteSetting.backup_drafts_to_pm_length = 1

      draft = { reply: "this is a reply", random_key: "random" }

      seq = Draft.set(user, "xyz", 0, draft.to_json)
      draft["reply"] = "test" * 100

      half_grace = (SiteSetting.editing_grace_period / 2 + 1).seconds

      freeze_time half_grace.from_now
      seq = Draft.set(user, "xyz", seq, draft.to_json)

      draft_post = BackupDraftPost.find_by(user_id: user.id, key: "xyz").post

      expect(draft_post.revisions.count).to eq(0)

      freeze_time half_grace.from_now

      # this should trigger a post revision as 10 minutes have passed
      draft["reply"] = "hello"
      Draft.set(user, "xyz", seq, draft.to_json)

      draft_topic = BackupDraftTopic.find_by(user_id: user.id)
      expect(draft_topic.topic.posts_count).to eq(2)

      draft_post.reload
      expect(draft_post.revisions.count).to eq(1)
    end
  end

  it "can get a draft by user" do
    Draft.set(user, "test", 0, "data")
    expect(Draft.get(user, "test", 0)).to eq "data"
  end

  it "uses the user id and key correctly" do
    Draft.set(user, "test", 0, "data")
    expect(Draft.get(Fabricate.build(:coding_horror), "test", 0)).to eq nil
  end

  it "should overwrite draft data correctly" do
    seq = Draft.set(user, "test", 0, "data")
    seq = Draft.set(user, "test", seq, "new data")
    expect(Draft.get(user, "test", seq)).to eq "new data"
  end

  it "should increase the sequence on every save" do
    seq = Draft.set(user, "test", 0, "data")
    expect(seq).to eq(0)
    seq = Draft.set(user, "test", 0, "data")
    expect(seq).to eq(1)
  end

  it "should clear drafts on request" do
    Draft.set(user, "test", 0, "data")
    Draft.clear(user, "test", 0)
    expect(Draft.get(user, "test", 0)).to eq nil
  end

  it "should cross check with DraftSequence table" do
    Draft.set(user, "test", 0, "old")
    expect(Draft.get(user, "test", 0)).to eq "old"

    DraftSequence.next!(user, "test")
    seq = DraftSequence.next!(user, "test")
    expect(seq).to eq(2)

    expect do Draft.set(user, "test", seq - 1, "error") end.to raise_error(Draft::OutOfSequence)

    expect do Draft.set(user, "test", seq + 1, "error") end.to raise_error(Draft::OutOfSequence)

    Draft.set(user, "test", seq, "data")
    expect(Draft.get(user, "test", seq)).to eq "data"

    expect do expect(Draft.get(user, "test", seq - 1)).to eq "data" end.to raise_error(
      Draft::OutOfSequence,
    )

    expect do expect(Draft.get(user, "test", seq + 1)).to eq "data" end.to raise_error(
      Draft::OutOfSequence,
    )
  end

  it "should disregard old draft if sequence decreases" do
    Draft.set(user, "test", 0, "data")
    DraftSequence.next!(user, "test")
    Draft.set(user, "test", 1, "hello")

    expect do Draft.set(user, "test", 0, "foo") end.to raise_error(Draft::OutOfSequence)

    expect do Draft.get(user, "test", 0) end.to raise_error(Draft::OutOfSequence)

    expect(Draft.get(user, "test", 1)).to eq "hello"
  end

  it "should disregard draft sequence if force_save is true" do
    Draft.set(user, "test", 0, "data")
    DraftSequence.next!(user, "test")
    Draft.set(user, "test", 1, "hello")

    seq = Draft.set(user, "test", 0, "foo", nil, force_save: true)
    expect(seq).to eq(2)
  end

  it "can cleanup old drafts" do
    key = Draft::NEW_TOPIC

    Draft.set(user, key, 0, "draft")

    Draft.cleanup!
    expect(Draft.count).to eq 1
    expect(user.user_stat.draft_count).to eq(1)

    seq = DraftSequence.next!(user, key)

    Draft.set(user, key, seq, "draft")
    DraftSequence.update_all("sequence = sequence + 1")

    draft = Draft.first
    draft.upload_references << UploadReference.create!(target: draft, upload: Fabricate(:upload))

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

    Draft.cleanup!

    expect(Draft.count).to eq 0
    expect(user.reload.user_stat.draft_count).to eq(0)
    expect(UploadReference.count).to eq(0)

    Draft.set(Fabricate(:user), Draft::NEW_TOPIC, 0, "draft")

    Draft.cleanup!

    expect(Draft.count).to eq 1

    # should cleanup drafts more than 180 days old
    SiteSetting.delete_drafts_older_than_n_days = 180

    Draft.last.update_columns(updated_at: 200.days.ago)
    Draft.cleanup!
    expect(Draft.count).to eq 0
  end

  it "updates draft count when a draft is created or destroyed" do
    Draft.set(Fabricate(:user), Draft::NEW_TOPIC, 0, "data")

    messages =
      MessageBus.track_publish("/user-drafts/#{user.id}") do
        Draft.set(user, Draft::NEW_TOPIC, 0, "data")
      end

    expect(messages.first.data[:draft_count]).to eq(1)
    expect(messages.first.data[:has_topic_draft]).to eq(true)
    expect(messages.first.user_ids).to contain_exactly(user.id)

    messages =
      MessageBus.track_publish("/user-drafts/#{user.id}") { Draft.where(user: user).destroy_all }

    expect(messages.first.data[:draft_count]).to eq(0)
    expect(messages.first.data[:has_topic_draft]).to eq(false)
    expect(messages.first.user_ids).to contain_exactly(user.id)
  end

  describe "#stream" do
    fab!(:public_post) { Fabricate(:post) }
    let(:public_topic) { public_post.topic }

    let(:stream) { Draft.stream(user: user) }

    it "should include the correct number of drafts in the stream" do
      Draft.set(user, "test", 0, '{"reply":"hey.","action":"createTopic","title":"Hey"}')
      Draft.set(user, "test2", 0, '{"reply":"howdy"}')
      expect(stream.count).to eq(2)
    end

    it "should include the right topic id in a draft reply in the stream" do
      Draft.set(user, "topic_#{public_topic.id}", 0, '{"reply":"hi"}')
      draft_row = stream.first
      expect(draft_row.topic_id).to eq(public_topic.id)
    end

    it "should include the right draft username in the stream" do
      Draft.set(user, "topic_#{public_topic.id}", 0, '{"reply":"hey"}')
      draft_row = stream.first
      expect(draft_row.user.username).to eq(user.username)
    end
  end

  describe "key expiry" do
    it "nukes new topic draft after a topic is created" do
      Draft.set(user, Draft::NEW_TOPIC, 0, "my draft")
      _t = Fabricate(:topic, user: user, advance_draft: true)
      s = DraftSequence.current(user, Draft::NEW_TOPIC)
      expect(Draft.get(user, Draft::NEW_TOPIC, s)).to eq nil
      expect(Draft.count).to eq 0
    end

    it "nukes new pm draft after a pm is created" do
      Draft.set(user, Draft::NEW_PRIVATE_MESSAGE, 0, "my draft")
      t =
        Fabricate(
          :topic,
          user: user,
          archetype: Archetype.private_message,
          category_id: nil,
          advance_draft: true,
        )
      s = DraftSequence.current(t.user, Draft::NEW_PRIVATE_MESSAGE)
      expect(Draft.get(user, Draft::NEW_PRIVATE_MESSAGE, s)).to eq nil
    end

    it "does not nuke new topic draft after a pm is created" do
      Draft.set(user, Draft::NEW_TOPIC, 0, "my draft")
      t = Fabricate(:topic, user: user, archetype: Archetype.private_message, category_id: nil)
      s = DraftSequence.current(t.user, Draft::NEW_TOPIC)
      expect(Draft.get(user, Draft::NEW_TOPIC, s)).to eq "my draft"
    end

    it "nukes the post draft when a post is created" do
      topic = Fabricate(:topic)

      Draft.set(user, topic.draft_key, 0, "hello")

      p =
        PostCreator.new(
          user,
          raw: Fabricate.build(:post).raw,
          topic_id: topic.id,
          advance_draft: true,
        ).create

      expect(
        Draft.get(p.user, p.topic.draft_key, DraftSequence.current(p.user, p.topic.draft_key)),
      ).to eq nil
    end

    it "nukes the post draft when a post is revised" do
      Draft.set(post.user, post.topic.draft_key, 0, "hello")
      post.revise(post.user, raw: "another test")
      s = DraftSequence.current(post.user, post.topic.draft_key)
      expect(Draft.get(post.user, post.topic.draft_key, s)).to eq nil
    end

    it "increases revision each time you set" do
      Draft.set(user, "new_topic", 0, "hello")
      Draft.set(user, "new_topic", 0, "goodbye")

      expect(Draft.find_by(user_id: user.id, draft_key: "new_topic").revisions).to eq(2)
    end

    it "handles owner switching gracefully" do
      draft_seq = Draft.set(user, "new_topic", 0, "hello", _owner = "ABCDEF")
      expect(draft_seq).to eq(0)

      draft_seq = Draft.set(user, "new_topic", 0, "hello world", _owner = "HIJKL")
      expect(draft_seq).to eq(1)

      draft_seq = Draft.set(user, "new_topic", 1, "hello world", _owner = "HIJKL")
      expect(draft_seq).to eq(2)
    end

    it "can correctly preload drafts" do
      Draft.set(
        user,
        "#{Draft::EXISTING_TOPIC}#{post.topic_id}",
        0,
        { raw: "hello", postId: post.id }.to_json,
      )

      drafts = Draft.where(user_id: user.id).to_a

      Draft.preload_data(drafts, user)

      expect(drafts[0].topic_preloaded?).to eq(true)
      expect(drafts[0].topic.id).to eq(post.topic_id)

      expect(drafts[0].post_preloaded?).to eq(true)
      expect(drafts[0].post.id).to eq(post.id)
    end
  end

  it { is_expected.to validate_length_of(:draft_key).is_at_most(25) }
end