# frozen_string_literal: true

RSpec.shared_examples 'issuable quick actions' do
  before do
    stub_const('QuickAction', Struct.new(:action_text, :expectation, :before_action, keyword_init: true) do
      # Pass a block as :before_action if
      # issuable state needs to be changed before
      # the quick action is executed.
      def call_before_action
        before_action.call if before_action
      end

      def skip_access_check
        action_text["/todo"] ||
          action_text["/done"] ||
          action_text["/subscribe"] ||
          action_text["/shrug"] ||
          action_text["/tableflip"]
      end
    end)
  end

  let(:unlabel_expectation) do
    ->(noteable, can_use_quick_action) {
      if can_use_quick_action
        expect(noteable.labels).to be_empty
      else
        expect(noteable.labels).not_to be_empty
      end
    }
  end

  # Quick actions shared by issues and merge requests
  let(:issuable_quick_actions) do
    [
      QuickAction.new(
        action_text: "/subscribe",
        expectation: ->(noteable, can_use_quick_action) {
          if can_use_quick_action
            expect(noteable).to be_subscribed(note_author, issuable.project)
          else
            expect(noteable).not_to be_subscribed(note_author, issuable.project)
          end
        }
      ),
      QuickAction.new(
        action_text: "/unsubscribe",
        expectation: ->(noteable, can_use_quick_action) {
          expect(noteable).not_to be_subscribed(note_author, issuable.project)
        }
      ),
      QuickAction.new(
        action_text: "/todo",
        expectation: ->(noteable, can_use_quick_action) {
          expect(noteable.todos.count == 1).to eq(can_use_quick_action)
        }
      ),
      QuickAction.new(
        action_text: "/done",
        expectation: ->(noteable, can_use_quick_action) {
          expect(noteable.todos.last.done?).to eq(can_use_quick_action)
        }
      ),
      QuickAction.new(
        action_text: "/close",
        expectation: ->(noteable, can_use_quick_action) {
          expect(noteable.closed?).to eq(can_use_quick_action)
        }
      ),
      QuickAction.new(
        action_text: "/reopen",
        before_action: -> {
          issuable.close
        },
        expectation: ->(noteable, can_use_quick_action) {
          expect(noteable.open?).to eq(can_use_quick_action)
        }
      ),
      QuickAction.new(
        action_text: "/assign @#{user.username}",
        expectation: ->(noteable, can_use_quick_action) {
          if noteable.allows_multiple_assignees?
            expect(noteable.assignees == [old_assignee, user]).to eq(can_use_quick_action)
          else
            expect(noteable.assignees == [user]).to eq(can_use_quick_action)
          end
        }
      ),
      QuickAction.new(
        action_text: "/unassign",
        expectation: ->(noteable, can_use_quick_action) {
          if can_use_quick_action
            expect(noteable.assignees).to be_empty
          else
            expect(noteable.assignees).not_to be_empty
          end
        }
      ),
      QuickAction.new(
        action_text: "/title new title",
        expectation: ->(noteable, can_use_quick_action) {
          expect(noteable.title == "new title").to eq(can_use_quick_action)
        }
      ),
      QuickAction.new(
        action_text: "/lock",
        expectation: ->(noteable, can_use_quick_action) {
          expect(noteable.discussion_locked?).to eq(can_use_quick_action)
        }
      ),
      QuickAction.new(
        action_text: "/unlock",
        before_action: -> {
          issuable.update!(discussion_locked: true)
        },
        expectation: ->(noteable, can_use_quick_action) {
          if can_use_quick_action
            expect(noteable).not_to be_discussion_locked
          else
            expect(noteable).to be_discussion_locked
          end
        }
      ),
      QuickAction.new(
        action_text: "/milestone %\"sprint\"",
        expectation: ->(noteable, can_use_quick_action) {
          expect(noteable.milestone == milestone).to eq(can_use_quick_action)
        }
      ),
      QuickAction.new(
        action_text: "/remove_milestone",
        before_action: -> {
          issuable.update!(milestone_id: milestone.id)
        },
        expectation: ->(noteable, can_use_quick_action) {
          if can_use_quick_action
            expect(noteable.milestone_id).to be_nil
          else
            expect(noteable.milestone_id).to eq(milestone.id)
          end
        }
      ),
      QuickAction.new(
        action_text: "/label ~feature",
        expectation: ->(noteable, can_use_quick_action) {
          expect(noteable.labels&.last&.id == feature_label.id).to eq(can_use_quick_action)
        }
      ),
      QuickAction.new(
        action_text: "/labels ~feature",
        expectation: ->(noteable, can_use_quick_action) {
          expect(noteable.labels&.last&.id == feature_label.id).to eq(can_use_quick_action)
        }
      ),
      QuickAction.new(
        action_text: "/unlabel",
        expectation: unlabel_expectation
      ),
      QuickAction.new(
        action_text: "/remove_label",
        expectation: unlabel_expectation
      ),
      QuickAction.new(
        action_text: "/award :100:",
        expectation: ->(noteable, can_use_quick_action) {
          if can_use_quick_action
            expect(noteable.award_emoji.last.name).to eq("100")
          else
            expect(noteable.award_emoji).to be_empty
          end
        }
      ),
      QuickAction.new(
        action_text: "/react :heart:",
        expectation: ->(noteable, can_use_quick_action) {
          if can_use_quick_action
            expect(noteable.award_emoji.last.name).to eq("heart")
          else
            expect(noteable.award_emoji).to be_empty
          end
        }
      ),
      QuickAction.new(
        action_text: "/estimate 1d 2h 3m",
        expectation: ->(noteable, can_use_quick_action) {
          expect(noteable.time_estimate == 36180).to eq(can_use_quick_action)
        }
      ),
      QuickAction.new(
        action_text: "/remove_estimate",
        before_action: -> {
          issuable.update!(time_estimate: 30000)
        },
        expectation: ->(noteable, can_use_quick_action) {
          if can_use_quick_action
            expect(noteable.time_estimate).to be_zero
          else
            expect(noteable.time_estimate).to eq(30000)
          end
        }
      ),
      QuickAction.new(
        action_text: "/spend 1d 2h 3m",
        expectation: ->(noteable, can_use_quick_action) {
          expect(noteable.total_time_spent == 36180).to eq(can_use_quick_action)
        }
      ),
      QuickAction.new(
        action_text: "/remove_time_spent",
        expectation: ->(noteable, can_use_quick_action) {
          if can_use_quick_action
            expect(noteable.total_time_spent == 0)
          else
            expect(noteable.timelogs).to be_empty
          end
        }
      ),
      QuickAction.new(
        action_text: "/shrug oops",
        expectation: ->(noteable, can_use_quick_action) {
          expect(noteable.notes&.last&.note == "HELLO\n¯\\＿(ツ)＿/¯\nWORLD").to eq(can_use_quick_action)
        }
      ),
      QuickAction.new(
        action_text: "/tableflip oops",
        expectation: ->(noteable, can_use_quick_action) {
          expect(noteable.notes&.last&.note == "HELLO\n(╯°□°)╯︵ ┻━┻\nWORLD").to eq(can_use_quick_action)
        }
      ),
      QuickAction.new(
        action_text: "/copy_metadata #{issue_2.to_reference}",
        expectation: ->(noteable, can_use_quick_action) {
          if can_use_quick_action
            expect(noteable.labels).to eq(issue_2.labels)
            expect(noteable.milestone).to eq(issue_2.milestone)
          else
            expect(noteable.labels).not_to eq(issue_2.labels)
            expect(noteable.milestone).not_to eq(issue_2.milestone)
          end
        }
      )
    ]
  end

  let(:old_assignee) { create(:user) }

  before do
    project.add_developer(old_assignee)
    issuable.update!(assignees: [old_assignee])
  end

  context 'when user can update issuable' do
    let_it_be(:developer) { create(:user) }

    let(:note_author) { developer }

    before do
      project.add_developer(developer)
    end

    it 'saves the note and updates the issue' do
      quick_actions.each do |quick_action|
        note_text = %(HELLO\n#{quick_action.action_text}\nWORLD)
        quick_action.call_before_action

        note = described_class.new(project, developer, note_params.merge(note: note_text)).execute
        noteable = note.noteable

        # shrug and tablefip quick actions modifies the note text
        # on these cases we need to skip this assertion
        if !quick_action.action_text["shrug"] && !quick_action.action_text["tableflip"]
          expect(note.note).to eq "HELLO\nWORLD"
        end

        quick_action.expectation.call(noteable, true)
      end
    end
  end

  context 'when user cannot update issuable' do
    let_it_be(:non_member) { create(:user) }

    let(:note_author) { non_member }

    it 'applies commands that user can execute' do
      quick_actions.each do |quick_action|
        note_text = %(HELLO\n#{quick_action.action_text}\nWORLD)
        quick_action.call_before_action

        note = described_class.new(project, non_member, note_params.merge(note: note_text)).execute
        noteable = note.noteable

        if quick_action.skip_access_check
          quick_action.expectation.call(noteable, true)
        else
          quick_action.expectation.call(noteable, false)
        end
      end
    end
  end
end
