# frozen_string_literal: true

RSpec.describe GDK::Command::Update do
  include ShelloutHelper

  let(:sh) { gdk_shellout_double(success?: true) }
  let(:gdk_available_after_update?) { true }

  before do
    allow(GDK::Hooks).to receive(:execute_hooks)
    allow(GDK).to receive(:make).and_return(sh)
    allow_gdk_shellout_command(%w[git rev-parse HEAD], chdir: GDK.config.gdk_root).and_return(
      gdk_shellout_double(success?: true, run: 'abcdef1234')
    )
    reconfigure_task = instance_double(Rake::Task, invoke: nil)
    allow(Rake::Task).to receive(:[]).with(:reconfigure).and_return(reconfigure_task)
    allow(Utils).to receive(:executable_exist_via_tooling_manager?).and_return(gdk_available_after_update?)

    gdk_migrate_task = instance_double(Rake::Task, invoke: nil)
    allow(Rake::Task).to receive(:[]).with('gdk:migrate').and_return(gdk_migrate_task)

    # Stubs for run_weekly_diagnostics
    allow_gdk_shellout_command('asdf').and_return(
      gdk_shellout_double(success?: true, readlines: [])
    )
    allow(GDK.config).to receive(:__cache_dir).and_return(Pathname.new('/fake/cache'))
    allow(FileUtils).to receive(:mkdir_p)
    allow(File).to receive(:exist?).and_return(false)
    allow(File).to receive(:write)
  end

  describe '#run', :hide_output do
    let(:env) { { 'PG_AUTO_UPDATE' => '1' } }

    context 'when self-update is enabled' do
      it 'runs self-update and update' do
        expect(GDK).to receive(:make).with('self-update')
        task = instance_double(Rake::Task, invoke: nil)
        expect(Rake::Task).to receive(:[]).with(:update).and_return(task)
        expect_any_instance_of(GDK::Announcements).to receive(:render_all)
        expect(GDK::Output).to receive(:success).with('Successfully updated!')

        subject.run
      end

      context 'when the sha was updated' do
        after do
          ENV['GDK_SELF_UPDATE'] = nil
        end

        it 'runs "gdk update" with the exec syscall' do
          expect(GDK).to receive(:make).with('self-update') do
            expect_gdk_shellout.with(%w[git rev-parse HEAD], chdir: GDK.config.gdk_root).and_return(
              gdk_shellout_double(success?: true, run: 'd06f00d')
            )
            sh
          end
          error = 'Kernel.exec stops and replaces the current process'
          expect(Kernel).to receive(:exec).with('gdk update').and_raise(error)
          expect(Dir).to receive(:chdir).with(GDK.config.gdk_root.to_s)

          expect { subject.run }.to raise_error(error)
          expect(ENV.fetch('GDK_SELF_UPDATE', nil)).to eq('0')
        end
      end
    end

    context 'when self-update is disabled' do
      before do
        stub_env('GDK_SELF_UPDATE', '0')
      end

      it 'only runs update' do
        expect(GDK).not_to receive(:make).with('self-update')
        task = instance_double(Rake::Task, invoke: nil)
        expect(Rake::Task).to receive(:[]).with(:update).and_return(task)
        expect(task).to receive(:invoke)
        expect_any_instance_of(GDK::Announcements).to receive(:render_all)
        expect(GDK::Output).to receive(:success).with('Successfully updated!')

        subject.run
      end
    end

    context 'when update fails' do
      it 'displays an error message', hide_output: false do
        stub_no_color_env('true')
        expect(GDK).to receive(:make).with('self-update')
        task = instance_double(Rake::Task)
        expect(Rake::Task).to receive(:[]).with(:update).and_return(task)
        expect(task).to receive(:invoke).and_raise('test error')
        expect_any_instance_of(GDK::Announcements).not_to receive(:render_all)

        expect { subject.run }
          .to output(/ERROR: test error\nERROR: Failed to update/).to_stderr
          .and output(/You can try the following that may be of assistance/).to_stdout
      end
    end

    context 'when migrate fails' do
      it 'displays an error message', hide_output: false do
        stub_no_color_env('true')
        expect(GDK).to receive(:make).with('self-update')

        migrate_task = instance_double(Rake::Task)
        expect(Rake::Task).to receive(:[]).with('gdk:migrate').and_return(migrate_task)
        expect(migrate_task).to receive(:invoke).and_raise('migration error')

        expect_any_instance_of(GDK::Announcements).not_to receive(:render_all)

        expect { subject.run }
          .to output(/ERROR: migration error\nERROR: Failed to update/).to_stderr
          .and output(/You can try the following that may be of assistance/).to_stdout
      end
    end

    context 'when reconfigure fails' do
      it 'returns false' do
        expect(subject).to receive(:run_rake).with('gdk:migrate').and_return(true)
        expect(subject).to receive(:run_rake).with(:update).and_return(true)
        expect(subject).to receive(:run_rake).with(:reconfigure).and_return(false)
        expect_any_instance_of(GDK::Announcements).not_to receive(:render_all)

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

    it 'delegates to #update! and executes with success' do
      expect(subject).to receive(:update!).and_return('some content')
      expect(subject).to receive(:run_rake).with(:reconfigure).and_return(true)
      expect(GDK::Output).to receive(:success).with('Successfully updated!')

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

    it 'prints a duration summary' do
      task = instance_double(Rake::Task, invoke: nil)
      allow(Rake::Task).to receive(:[]).with(:update).and_return(task)
      expect(GDK::Output).to receive(:success).with('Successfully updated!')

      subject.run
    end

    context 'when gdk.auto_reconfigure flag is disabled' do
      before do
        yaml = {
          'gdk' => {
            'auto_reconfigure' => false
          }
        }
        stub_gdk_yaml(yaml)
      end

      it 'does not execute reconfigure command after update' do
        expect(subject).to receive(:update!).and_return('some content')
        expect(subject).not_to receive(:run_rake).with(:reconfigure)
        expect(GDK::Output).to receive(:success).with('Successfully updated!')

        subject.run
      end
    end

    context 'when `gdk` command no longer available after update' do
      let(:gdk_available_after_update?) { false }

      it 'prints an error' do
        expect(subject).to receive(:update!).and_return(true)
        expect(subject).to receive(:run_rake).with(:reconfigure).and_return(true)
        expect(GDK::Telemetry).to receive(:capture_exception).with(described_class::GdkNotFoundError.new('`gdk` command is no longer available'))
        expect(GDK::Output).to receive(:error).with('The `gdk` is no longer available after `gdk update`. This is unexpected, please report this in https://gitlab.com/gitlab-org/gitlab-development-kit/-/issues/2388.')

        subject.run
      end
    end
  end
end
