# frozen_string_literal: true

RSpec.shared_examples 'component updater' do |project_class, merge_request_class, slack_notification_method|
  let(:token) { 'a token' }

  # Use the subject provided by the including spec
  # This allows us to handle both class and module cases

  describe '.new' do
    context 'when GITLAB_BOT_PRODUCTION_TOKEN is not available' do
      let(:token) { nil }

      it 'raises error' do
        ClimateControl.modify(GITLAB_BOT_PRODUCTION_TOKEN: nil) do
          expect { task }.to raise_error('key not found: "GITLAB_BOT_PRODUCTION_TOKEN"')
        end
      end
    end
  end

  describe '#execute' do
    context 'when there are no changes' do
      it 'does nothing' do
        expect(task).to receive(:changed?).and_return(false)

        expect(task).not_to receive(:merge_request)
        expect(task).not_to receive(:ensure_source_branch_exists)
        expect(task).not_to receive(:create_or_show_merge_request)
        expect(ReleaseTools::Services::UpdateComponentService)
          .not_to receive(:new)
        expect(ReleaseTools::Services::AutoMergeService).not_to receive(:new)

        without_dry_run do
          task.execute
        end
      end
    end

    context 'when the merge request already exists' do
      let(:merge_request) do
        instance_double(
          ReleaseTools::UpdateKasMergeRequest,
          exists?: true,
          url: 'an url',
          merge_when_pipeline_succeeds?: false,
          remote_issuable: build(:merge_request),
          detailed_merge_status: merge_status,
          notifiable?: notifiable
        )
      end

      let(:notifiable) { true }

      before do
        allow(task).to receive(:merge_request).and_return(merge_request)
      end

      context 'when the merge request is mergeable' do
        %w[mergeable unchecked not_approved].each do |merge_status|
          let(:merge_status) { merge_status }

          it "sets auto merge for the merge request with status #{merge_status}" do
            expect(task).to receive(:changed?).and_return(true)

            expect(task).to receive(:create_merge_request_and_set_auto_merge)
            expect(task).not_to receive(:notify_stale_merge_request)

            without_dry_run do
              task.execute
            end
          end
        end
      end

      context 'when the merge request is not mergeable' do
        context 'when the merge request is notifiable' do
          let(:merge_status) { 'ci_still_running' }

          it 'sends a notification if MR is stale' do
            expect(task).to receive(:changed?).and_return(true)

            expect(task).not_to receive(:create_merge_request_and_set_auto_merge)

            if slack_notification_method
              expect(ReleaseTools::Slack::AutoDeployNotification)
                .to receive(slack_notification_method)
                .with(merge_request)
            else
              expect(task).to receive(:send_slack_notification)
            end

            expect(merge_request).to receive(:mark_as_stale)

            without_dry_run do
              task.execute
            end
          end
        end

        context 'when the merge request is not notifiable' do
          let(:merge_status) { 'ci_still_running' }
          let(:notifiable) { false }

          it 'does not notify' do
            expect(task).to receive(:changed?).and_return(true)

            expect(task).not_to receive(:create_merge_request_and_set_auto_merge)
            expect(task).not_to receive(:notify_stale_merge_request)

            without_dry_run do
              task.execute
            end
          end
        end
      end
    end

    context 'when the merge request does not exist' do
      it 'makes sure the source branch exists, creates the merge request, updates component versions and auto-merge' do
        expect(task).to receive(:changed?).and_return(true)
        merge_request = instance_double(merge_request_class, exists?: false, project: double)
        allow(task).to receive(:merge_request).and_return(merge_request)

        expect(task).to receive(:ensure_source_branch_exists)
        expect(task).to receive(:create_or_show_merge_request).with(merge_request)

        update_component_service = instance_double(ReleaseTools::Services::UpdateComponentService)
        expect(ReleaseTools::Services::UpdateComponentService)
          .to receive(:new)
          .with(project_class, task.source_branch, { skip_ci: true })
          .and_return(update_component_service)

        commit = instance_double(ReleaseTools::Commits)
        expect(update_component_service).to receive(:execute).and_return(commit)

        auto_merge_service = instance_double(ReleaseTools::Services::AutoMergeService)
        expect(ReleaseTools::Services::AutoMergeService).to receive(:new).with(merge_request, { token: token, commit: commit }).and_return(auto_merge_service)
        expect(auto_merge_service).to receive(:execute)

        without_dry_run do
          task.execute
        end
      end
    end
  end

  describe '#ensure_source_branch_exist' do
    it 'delegates to GitlabClient.find_or_create_branch' do
      branch = instance_double(ReleaseTools::Branch)

      # Use TARGET_PROJECT from the class if it's a class constant, otherwise use the project parameter
      target_project = defined?(described_class::TARGET_PROJECT) ? described_class::TARGET_PROJECT : task.class::TARGET_PROJECT

      expect(ReleaseTools::GitlabClient)
        .to receive(:find_or_create_branch)
        .with(task.source_branch, 'master', target_project)
        .and_return(branch)

      without_dry_run do
        expect(task.ensure_source_branch_exists).to eq(branch)
      end
    end
  end

  describe '#changed?' do
    it 'checks for changes in master' do
      changed = double('changed?') # rubocop:disable RSpec/VerifiedDoubles
      expect(ReleaseTools::Services::UpdateComponentService)
        .to receive(:new)
        .with(project_class, 'master')
        .and_return(instance_double(ReleaseTools::Services::UpdateComponentService, changed?: changed))

      expect(task.changed?).to eq(changed)
    end
  end

  describe '#merge_request' do
    it 'is a merge request from source_branch' do
      expect(merge_request_class)
        .to receive(:new)
        .with({ source_branch: task.source_branch })

      task.merge_request
    end
  end
end
