# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Gitlab::QuickActions::CommandDefinition do
  let(:mock_command) { :command }

  subject { described_class.new(mock_command) }

  describe "#all_names" do
    context "when the command has aliases" do
      before do
        subject.aliases = [:alias1, :alias2]
      end

      it "returns an array with the name and aliases" do
        expect(subject.all_names).to eq([:command, :alias1, :alias2])
      end
    end

    context "when the command doesn't have aliases" do
      it "returns an array with the name" do
        expect(subject.all_names).to eq([:command])
      end
    end
  end

  describe "#noop?" do
    context "when the command has an action block" do
      before do
        subject.action_block = proc {}
      end

      it "returns false" do
        expect(subject.noop?).to be false
      end
    end

    context "when the command doesn't have an action block" do
      it "returns true" do
        expect(subject.noop?).to be true
      end
    end
  end

  describe "#available?" do
    let(:opts) { ActiveSupport::InheritableOptions.new(go: false) }

    context "when the command has a condition block" do
      before do
        subject.condition_block = proc { go }
      end

      context "when the condition block returns true" do
        before do
          opts[:go] = true
        end

        it "returns true" do
          expect(subject.available?(opts)).to be true
        end
      end

      context "when the condition block returns false" do
        it "returns false" do
          expect(subject.available?(opts)).to be false
        end
      end
    end

    context "when the command doesn't have a condition block" do
      it "returns true" do
        expect(subject.available?(opts)).to be true
      end
    end

    context "when the command has types" do
      before do
        subject.types = [Issue, Commit, WorkItem]
      end

      context "when the command target type is allowed" do
        it "returns true" do
          opts[:quick_action_target] = Issue.new
          expect(subject.available?(opts)).to be true
        end

        context 'when the command target type is Work Item' do
          context 'when the command is not allowed' do
            it "returns false" do
              opts[:quick_action_target] = build(:work_item)
              expect(subject.available?(opts)).to be false
            end
          end

          context 'when the command is allowed' do
            it "returns true" do
              allow_next_instance_of(WorkItem) do |work_item|
                allow(work_item).to receive(:supported_quick_action_commands).and_return([mock_command])
              end

              opts[:quick_action_target] = build(:work_item)
              expect(subject.available?(opts)).to be true
            end
          end
        end
      end

      context "when the command target type is not allowed" do
        it "returns true" do
          opts[:quick_action_target] = MergeRequest.new
          expect(subject.available?(opts)).to be false
        end
      end
    end

    context "when the command has no types" do
      it "any target type is allowed" do
        opts[:quick_action_target] = Issue.new
        expect(subject.available?(opts)).to be true

        opts[:quick_action_target] = MergeRequest.new
        expect(subject.available?(opts)).to be true

        opts[:quick_action_target] = build(:work_item)
        expect(subject.available?(opts)).to be true
      end
    end
  end

  describe "#execute" do
    let(:fake_context) { Struct.new(:run, :commands_executed_count, :received_arg) }
    let(:context) { fake_context.new(false, nil, nil) }

    context "when the command is a noop" do
      it "doesn't execute the command" do
        expect(context).not_to receive(:instance_exec)

        subject.execute(context, nil)

        expect(context.commands_executed_count).to be_nil
        expect(context.run).to be false
      end
    end

    context "when the command is not a noop" do
      before do
        subject.action_block = proc { self.run = true }
      end

      context "when the command is not available" do
        before do
          subject.condition_block = proc { false }
        end

        it "counts the command as executed" do
          subject.execute(context, nil)

          expect(context.commands_executed_count).to eq(1)
          expect(context.run).to be false
        end
      end

      context "when the command is available" do
        context "when the command has no arguments" do
          before do
            subject.action_block = proc { self.run = true }
          end

          context "when the command is provided an argument" do
            it "executes the command" do
              subject.execute(context, true)

              expect(context.run).to be true
              expect(context.commands_executed_count).to eq(1)
            end
          end

          context "when the command is not provided an argument" do
            it "executes the command" do
              subject.execute(context, nil)

              expect(context.run).to be true
              expect(context.commands_executed_count).to eq(1)
            end
          end
        end

        context "when the command has 1 required argument" do
          before do
            subject.action_block = ->(arg) { self.run = arg }
          end

          context "when the command is provided an argument" do
            it "executes the command" do
              subject.execute(context, true)

              expect(context.run).to be true
              expect(context.commands_executed_count).to eq(1)
            end
          end

          context "when the command is not provided an argument" do
            it "doesn't execute the command" do
              subject.execute(context, nil)

              expect(context.run).to be false
            end
          end
        end

        context "when the command has 1 optional argument" do
          before do
            subject.action_block = proc { |arg = nil| self.run = arg || true }
          end

          context "when the command is provided an argument" do
            it "executes the command" do
              subject.execute(context, true)

              expect(context.run).to be true
            end
          end

          context "when the command is not provided an argument" do
            it "executes the command" do
              subject.execute(context, nil)

              expect(context.run).to be true
            end
          end
        end

        context 'when the command defines parse_params block' do
          before do
            subject.parse_params_block = ->(raw) { raw.strip }
            subject.action_block = ->(parsed) { self.received_arg = parsed }
          end

          it 'executes the command passing the parsed param' do
            subject.execute(context, 'something   ')

            expect(context.received_arg).to eq('something')
          end
        end
      end
    end
  end

  describe "#execute_message" do
    context "when the command is a noop" do
      it 'returns nil' do
        expect(subject.execute_message({}, nil)).to be_nil
      end
    end

    context "when the command is not a noop" do
      before do
        subject.action_block = proc { self.run = true }
      end

      context "when the command is not available" do
        before do
          subject.condition_block = proc { false }
        end

        it 'returns an error message' do
          expect(subject.execute_message({}, nil)).to eq('Could not apply command command.')
        end
      end

      context "when the command is available" do
        context 'when the execution_message is a static string' do
          before do
            subject.execution_message = 'Assigned jacopo'
          end

          it 'returns this static string' do
            expect(subject.execute_message({}, nil)).to eq('Assigned jacopo')
          end
        end

        context 'when the explanation is dynamic' do
          before do
            subject.execution_message = proc { |arg| "Assigned #{arg}" }
          end

          it 'invokes the proc' do
            expect(subject.execute_message({}, 'Jacopo')).to eq('Assigned Jacopo')
          end
        end
      end
    end
  end

  describe '#explain' do
    context 'when the command is not available' do
      before do
        subject.condition_block = proc { false }
        subject.explanation = 'Explanation'
      end

      it 'returns nil' do
        result = subject.explain({}, nil)

        expect(result).to be_nil
      end
    end

    context 'when the explanation is a static string' do
      before do
        subject.explanation = 'Explanation'
      end

      it 'returns this static string' do
        result = subject.explain({}, nil)

        expect(result).to eq 'Explanation'
      end
    end

    context 'when warning is set' do
      before do
        subject.explanation = 'Explanation'
        subject.warning = 'dangerous!'
      end

      it 'returns this static string' do
        result = subject.explain({}, nil)

        expect(result).to eq 'Explanation (dangerous!)'
      end
    end

    context 'when the explanation is dynamic' do
      before do
        subject.explanation = proc { |arg| "Dynamic #{arg}" }
      end

      it 'invokes the proc' do
        result = subject.explain({}, 'explanation')

        expect(result).to eq 'Dynamic explanation'
      end
    end
  end

  describe '.to_h' do
    context 'with conditional aliases' do
      before do
        subject.conditional_aliases = [:test]
      end

      it 'adds aliases if conditional aliases are available' do
        subject.conditional_aliases_block = proc { true }

        result = subject.to_h(context: {})

        expect(result[:aliases]).to eq([:test])
      end

      it 'does not add aliases if conditional aliases are not available' do
        subject.conditional_aliases_block = proc { false }

        result = subject.to_h(context: {})

        expect(result[:aliases]).to be_empty
      end
    end
  end
end
