# frozen_string_literal: true

require 'spec_helper'
require 'release_tools/tasks'

RSpec.describe ReleaseTools::Tasks::AutoBuild::Timer do
  subject(:task) { described_class.new }

  let(:schedule_env) { '0, 3, 6,21' }

  around do |ex|
    ClimateControl.modify(AUTO_DEPLOY_SCHEDULE: schedule_env) do
      ex.run
    end
  end

  describe '#execute' do
    let(:prepare_task) { instance_double(ReleaseTools::Tasks::AutoBuild::Prepare) }
    let(:tag_task) { instance_double(ReleaseTools::Tasks::AutoDeploy::Tag) }
    let(:pick_service) { instance_double(ReleaseTools::CherryPick::AutoDeployService) }
    let(:current_auto_deploy_branch) { 'auto-deploy-01' }
    let(:new_auto_deploy_branch) { 'auto-deploy-02' }

    tests = [
      {
        auto_build_time: true,
        no_auto_deploy_branch: true,
        should_prepare: true
      },
      {
        auto_build_time: true,
        no_auto_deploy_branch: false,
        should_prepare: false
      },
      {
        auto_build_time: false,
        no_auto_deploy_branch: false,
        should_prepare: false
      },
      {
        auto_build_time: false,
        no_auto_deploy_branch: true,
        should_prepare: false
      }
    ]

    before do
      allow(ReleaseTools::Tasks::AutoBuild::Prepare).to receive(:new).and_return(prepare_task)
      allow(ReleaseTools::Tasks::AutoDeploy::Tag).to receive(:new).and_return(tag_task)

      allow(task).to receive_messages(auto_build_time?: auto_build_time, no_auto_deploy_branch?: no_auto_deploy_branch)
    end

    around do |ex|
      ClimateControl.modify(AUTO_DEPLOY_BRANCH: current_auto_deploy_branch) do
        ex.run
      end
    end

    tests.each do |test_case|
      context "when auto_build_time: #{test_case[:auto_build_time]} no_auto_deploy_branch: #{test_case[:no_auto_deploy_branch]}" do
        let(:auto_build_time) { test_case[:auto_build_time] }
        let(:no_auto_deploy_branch) { test_case[:no_auto_deploy_branch] }

        it 'correctly handles prepare, pick, and tag' do
          branch_name = current_auto_deploy_branch

          if test_case[:should_prepare]
            expect(prepare_task).to receive(:execute).and_return(
              [
                ReleaseTools::Services::AutoDeployBranchService::Result.new(
                  ReleaseTools::Project::GitlabEe.auto_deploy_path,
                  new_auto_deploy_branch,
                  build(:branch, name: new_auto_deploy_branch),
                  false
                )
              ]
            )
            branch_name = new_auto_deploy_branch
          else
            expect(prepare_task).not_to receive(:execute)
          end

          expect(ReleaseTools::CherryPick::AutoDeployService).to receive(:new).with(ReleaseTools::Project::GitlabEe, branch_name).and_return(pick_service)
          expect(ReleaseTools::CherryPick::AutoDeployService).to receive(:new).with(ReleaseTools::Project::OmnibusGitlab, branch_name).and_return(pick_service)
          expect(ReleaseTools::CherryPick::AutoDeployService).to receive(:new).with(ReleaseTools::Project::CNGImage, branch_name).and_return(pick_service)
          expect(pick_service).to receive(:execute).exactly(3)
          expect(tag_task).to receive(:execute)

          task.execute
        end
      end
    end

    context 'when there are no new commits and auto deploy branch is not created' do
      let(:auto_build_time) { true }
      let(:no_auto_deploy_branch) { true }
      let(:pick_service) { instance_spy(ReleaseTools::CherryPick::AutoDeployService) }
      let(:tag_task) { instance_spy(ReleaseTools::Tasks::AutoDeploy::Tag) }

      before do
        allow(ReleaseTools::CherryPick::AutoDeployService).to receive(:new).and_return(pick_service)
      end

      it 'does not modify AUTO_DEPLOY_BRANCH' do
        expect(ENV.fetch('AUTO_DEPLOY_BRANCH', nil)).to eq(current_auto_deploy_branch)
        expect(prepare_task).to receive(:execute).and_return([])

        task.execute

        expect(ENV.fetch('AUTO_DEPLOY_BRANCH', nil)).to eq(current_auto_deploy_branch)
      end
    end

    context 'with TAG_PACKAGERS_ONLY' do
      let(:auto_build_time) { true }
      let(:no_auto_deploy_branch) { false }

      it 'tags packagers only' do
        allow(tag_task).to receive(:execute)

        allow(ReleaseTools::CherryPick::AutoDeployService).to receive(:new).and_return(pick_service)
        allow(pick_service).to receive(:execute)

        expect(ReleaseTools::Tasks::AutoDeploy::Tag).to receive(:new).with(packagers_only: true)

        ClimateControl.modify(TAG_PACKAGERS_ONLY: 'true') do
          task.execute
        end
      end
    end
  end

  describe '#schedule' do
    it 'returns an array of integers' do
      expect(task.schedule).to eq([0, 3, 6, 21])
    end

    context 'when AUTO_DEPLOY_SCHEDULE is not set' do
      let(:schedule_env) { nil }

      it 'raise an error' do
        expect { task.schedule }.to raise_error('Missing schedule definition in AUTO_DEPLOY_SCHEDULE')
      end
    end

    context 'when AUTO_DEPLOY_SCHEDULE is empty' do
      let(:schedule_env) { '' }

      # this is important for manual pipelines that disable the schedule and
      # only perform pick and tag
      it 'returns an empty array' do
        expect(task.schedule).to be_empty
      end
    end

    context 'when AUTO_DEPLOY_SCHEDULE contains unexpected characters' do
      let(:schedule_env) { '0,f,3' }

      it 'raise an error' do
        expect { task.schedule }.to raise_error(ArgumentError)
      end
    end

    context 'when AUTO_DEPLOY_SCHEDULE contains more than 24 hours' do
      let(:schedule_env) { (0..66).to_a.join(',') }

      it 'raise an error' do
        expect { task.schedule }.to raise_error(ArgumentError)
      end
    end

    context 'when AUTO_DEPLOY_SCHEDULE contains number higher than 23' do
      let(:schedule_env) { '0,24,5' }

      it 'raise an error' do
        expect { task.schedule }.to raise_error(ArgumentError, '24 is not a valid hour of the day')
      end
    end

    context 'when AUTO_DEPLOY_SCHEDULE contains negative numbers' do
      let(:schedule_env) { '9,-6,5' }

      it 'raise an error' do
        expect { task.schedule }.to raise_error(ArgumentError, '-6 is not a valid hour of the day')
      end
    end
  end

  describe '#auto_build_time?' do
    it 'is auto build time when hour matches the schedule' do
      schedule_env.split(',') do |hour|
        now = Time.utc(2023, 5, 3, hour.to_i, 45)
        Timecop.travel(now) do
          expect(task).to be_auto_build_time
        end
      end
    end

    it 'is not auto build time when hour is outside of the schedule' do
      0.upto(23) do |hour|
        next if task.schedule.include?(hour)

        now = Time.utc(2023, 5, 3, hour.to_i, 45)
        Timecop.travel(now) do
          expect(task).not_to be_auto_build_time
        end
      end
    end

    it 'is not auto build time on weekend' do
      0.upto(23) do |hour|
        saturday = Time.utc(2023, 4, 29, hour.to_i, 45)
        Timecop.travel(saturday) do
          expect(task).not_to be_auto_build_time
        end
        Timecop.travel(saturday + 1.day) do
          expect(task).not_to be_auto_build_time
        end
      end
    end

    it 'can be forced true with CREATE_AUTO_DEPLOY_BRANCH_SCHEDULE' do
      ClimateControl.modify(CREATE_AUTO_DEPLOY_BRANCH_SCHEDULE: '1') do
        saturday = Time.utc(2023, 4, 29)
        Timecop.travel(saturday) do
          expect(task).to be_auto_build_time
        end
      end
    end
  end

  describe '#no_auto_deploy_branch?' do
    it 'returns true when the current auto-deploy branch does not exist' do
      branch = '16-0-auto-deploy-2023050308'
      expect(ReleaseTools::AutoDeploy::Naming).to receive(:branch).and_return(branch)
      expect(ReleaseTools::GitlabClient).to receive(:find_branch).with(branch, ReleaseTools::Project::GitlabEe.auto_deploy_path).and_return(nil)

      expect(task).to be_no_auto_deploy_branch
    end

    it 'returns false when the current auto-deploy branch exists' do
      branch = '16-0-auto-deploy-2023050308'
      expect(ReleaseTools::AutoDeploy::Naming).to receive(:branch).and_return(branch)
      expect(ReleaseTools::GitlabClient).to receive(:find_branch).with(branch, ReleaseTools::Project::GitlabEe.auto_deploy_path).and_return(build(:branch))

      expect(task).not_to be_no_auto_deploy_branch
    end
  end
end
