# frozen_string_literal: true

require 'spec_helper'

RSpec.describe ReleaseTools::Security::MergeTrainService do
  let(:client) { ReleaseTools::GitlabOpsClient }
  let(:project) { ReleaseTools::Project::GitlabEe }

  subject(:service) { described_class.new }

  def mirror_stub(overrides = {})
    defaults = {
      enabled: true,
      last_error: nil,
      update_status: 'finished',
      url: 'https://*****:*****@gitlab.com/gitlab-org/security/gitlab'
    }

    double(defaults.merge(overrides))
  end

  before do
    enable_feature(:internal_release_merge_train_stable_branches)
  end

  describe '#execute' do
    context 'when all branches need to be synced using merge-train' do
      before do
        # Stub the methods that are called within execute
        allow(service).to receive(:run_merge_train_default_branch)
        allow(service).to receive(:run_merge_train_stable_branches)
      end

      it 'calls run_merge_train_default_branch for each project with correct schedule_id' do
        without_dry_run do
          service.execute
        end

        described_class::PROJECTS.each do |project, schedule_id|
          expect(service).to have_received(:run_merge_train_default_branch)
            .with(project, schedule_id)
        end
      end

      it 'calls run_merge_train_stable_branches for each project' do
        without_dry_run do
          service.execute
        end

        described_class::PROJECTS_STABLE_BRANCHES.each_key do |project|
          expect(service).to have_received(:run_merge_train_stable_branches)
            .with(project, described_class::PROJECTS_STABLE_BRANCHES[project])
        end
      end

      it 'processes all projects in PROJECTS' do
        without_dry_run do
          service.execute
        end

        expect(service).to have_received(:run_merge_train_default_branch)
          .exactly(described_class::PROJECTS.count).times
        expect(service).to have_received(:run_merge_train_stable_branches)
          .exactly(described_class::PROJECTS_STABLE_BRANCHES.count).times
      end

      context 'when required and inactive' do
        before do
          allow(service).to receive(:run_merge_train_default_branch).and_call_original
          allow(service).to receive_messages(
            merge_train_required?: true,
            merge_train_active?: false
          )
        end

        it 'toggles on the merge-train pipeline schedule and notifies' do
          described_class::PROJECTS.each_value do |schedule_id|
            new_schedule = double

            expect(client).to receive(:pipeline_schedule_take_ownership).with(
              ReleaseTools::Project::MergeTrain,
              schedule_id
            )

            expect(client).to receive(:edit_pipeline_schedule).with(
              ReleaseTools::Project::MergeTrain.ops_path,
              schedule_id,
              active: true,
              cron: described_class::CRON
            ).and_return(new_schedule)

            expect(ReleaseTools::Slack::MergeTrainNotification)
            .to receive(:toggled)
            .with(new_schedule)
          end

          without_dry_run do
            service.execute
          end
        end
      end

      context 'when not required and active' do
        before do
          allow(service).to receive(:run_merge_train_default_branch).and_call_original
          allow(service).to receive_messages(
            merge_train_required?: false,
            merge_train_active?: true
          )
        end

        it 'toggles off the merge-train pipeline schedule and notifies' do
          described_class::PROJECTS.each_value do |schedule_id|
            new_schedule = double

            expect(client).to receive(:pipeline_schedule_take_ownership).with(
              ReleaseTools::Project::MergeTrain,
              schedule_id
            )

            expect(client).to receive(:edit_pipeline_schedule).with(
              ReleaseTools::Project::MergeTrain.ops_path,
              schedule_id,
              active: false,
              cron: described_class::CRON
            ).and_return(new_schedule)

            expect(ReleaseTools::Slack::MergeTrainNotification)
            .to receive(:toggled)
            .with(new_schedule)
          end

          without_dry_run do
            service.execute
          end
        end
      end

      context 'when required and active' do
        before do
          allow(service).to receive(:run_merge_train_default_branch).and_call_original
          allow(service).to receive_messages(
            merge_train_required?: true,
            merge_train_active?: true
          )
        end

        it 'does nothing' do
          expect(client).not_to receive(:pipeline_schedule_take_ownership)
          expect(client).not_to receive(:edit_pipeline_schedule)
          expect(ReleaseTools::Slack::MergeTrainNotification).not_to receive(:toggled)

          without_dry_run do
            service.execute
          end
        end
      end

      context 'when not required and inactive' do
        before do
          allow(service).to receive(:run_merge_train_default_branch).and_call_original
          allow(service).to receive_messages(
            merge_train_required?: false,
            merge_train_active?: false
          )
        end

        it 'does nothing' do
          expect(client).not_to receive(:pipeline_schedule_take_ownership)
          expect(client).not_to receive(:edit_pipeline_schedule)
          expect(ReleaseTools::Slack::MergeTrainNotification).not_to receive(:toggled)

          without_dry_run do
            service.execute
          end
        end
      end

      context 'when the feature flag is disabled' do
        before do
          disable_feature(:internal_release_merge_train_stable_branches)
        end

        it 'does not call run_merge_train_stable_branches for each project' do
          without_dry_run do
            service.execute
          end

          expect(service).to have_received(:run_merge_train_default_branch).exactly(3).times
          expect(service).not_to have_received(:run_merge_train_stable_branches)
        end
      end
    end

    context 'when the default branches do not need to be synced using merge-train' do
      before do
        # Ignore the implementation of run_merge_train_stable_branches
        allow(service).to receive(:run_merge_train_stable_branches)

        described_class::PROJECTS.each_key do |project|
          allow(service).to receive(:merge_train_required?).with(project).and_return(false)
        end
        allow(service).to receive(:merge_train_active?).and_return(false)
      end

      it 'does not enable any merge-train schedule' do
        expect(service).not_to receive(:toggle_merge_train).with(anything, true)

        without_dry_run do
          service.execute
        end
      end
    end

    context 'when the stable branches do not need to be synced using merge-train' do
      let(:branches) { ['15-6-stable-ee', '15-5-stable-ee'] }
      let(:project) { ReleaseTools::Project::GitlabEe }

      before do
        # Ignore the implementation of run_merge_train_default_branch
        allow(service).to receive(:run_merge_train_default_branch)

        allow(service).to receive(:internal_release_stable_branches).and_return(branches)

        branches.each do |branch|
          allow(service).to receive(:merge_train_required?).with(project, branch: branch).and_return(false)
          allow(service).to receive(:merge_train_active?).and_return(false)
        end
      end

      it 'does not enable any merge-train schedule' do
        expect(service).not_to receive(:toggle_merge_train).with(anything, true)

        without_dry_run do
          service.execute
        end
      end
    end

    context 'when an error happens with getting remote mirrors' do
      let(:project) { ReleaseTools::Project::GitlabEe }

      before do
        allow(service).to receive(:run_merge_train_stable_branches)
        described_class::PROJECTS.each_key do |project|
          allow(service).to receive(:get_remote_mirrors).with(project).and_raise(Gitlab::Error::Error)
        end
        allow(service).to receive(:merge_train_active?).and_return(false)
      end

      it 'does not enable any merge-train schedule' do
        expect(service).not_to receive(:toggle_merge_train).with(anything, true)

        without_dry_run do
          service.execute
        end
      end
    end
  end
end
