# frozen_string_literal: true

require_relative '../../spec_helper'

require 'vagrant-libvirt/action/destroy_domain'

describe VagrantPlugins::ProviderLibvirt::Action::DestroyDomain do
  subject { described_class.new(app, env) }

  include_context 'unit'
  include_context 'libvirt'

  let(:driver) { double('driver') }
  let(:libvirt_domain) { double('libvirt_domain') }
  let(:libvirt_client) { double('libvirt_client') }
  let(:servers) { double('servers') }

  let(:domain_xml) { File.read(File.join(File.dirname(__FILE__), File.basename(__FILE__, '.rb'), domain_xml_file)) }

  before do
    allow(machine.provider).to receive('driver').and_return(driver)
    allow(driver).to receive(:connection).and_return(connection)
    allow(logger).to receive(:info)
  end

  describe '#call' do
    before do
      allow(connection).to receive(:client).and_return(libvirt_client)
      allow(libvirt_client).to receive(:lookup_domain_by_uuid)
        .and_return(libvirt_domain)
      allow(libvirt_domain).to receive(:name).and_return('vagrant-test_default')
      allow(connection).to receive(:servers).and_return(servers)
      allow(servers).to receive(:get).and_return(domain)

      # always see this at the start of #call
      expect(ui).to receive(:info).with('Removing domain...')
    end

    context 'when no snapshots' do
      let(:root_disk) { double('libvirt_root_disk') }

      before do
        allow(libvirt_domain).to receive(:list_snapshots).and_return([])
        allow(libvirt_domain).to receive(:has_managed_save?).and_return(nil)
        allow(root_disk).to receive(:name).and_return('vagrant-test_default.img')
      end

      context 'when box only has one root disk' do
        it 'calls fog to destroy volumes' do
          expect(domain).to receive(:destroy).with(destroy_volumes: true, flags: 0)
          expect(subject.call(env)).to be_nil
        end

        context 'when has additional disks' do
          let(:vagrantfile_providerconfig) do
            <<-EOF
                libvirt.storage :file
            EOF
          end
          let(:domain_xml_file) { 'additional_disks_domain.xml' }
          let(:extra_disk) { double('libvirt_extra_disk') }

          before do
            allow(extra_disk).to receive(:name).and_return('vagrant-test_default-vdb.qcow2')
            allow(domain).to receive(:volumes).and_return([root_disk, extra_disk])
            expect(libvirt_domain).to receive(:xml_desc).and_return(domain_xml)
          end

          it 'destroys disks individually' do
            expect(domain).to receive(:destroy).with(destroy_volumes: false, flags: 0)
            expect(extra_disk).to receive(:destroy) # extra disk remove
            expect(root_disk).to receive(:destroy)  # root disk remove
            expect(subject.call(env)).to be_nil
          end
        end
      end

      context 'when box has multiple disks' do
        let(:domain_xml_file) { 'box_multiple_disks.xml' }

        it 'calls fog to destroy volumes' do
          expect(domain).to receive(:destroy).with(destroy_volumes: true, flags: 0)
          expect(subject.call(env)).to be_nil
        end

        context 'when has additional disks' do
          let(:domain_xml_file) { 'box_multiple_disks_and_additional_disks.xml' }
          let(:vagrantfile_providerconfig) do
            <<-EOF
                libvirt.storage :file
                libvirt.storage :file
            EOF
          end
          let(:domain_disks) {[
            [double('box-disk-1'), 'vagrant-test_default.img'],
            [double('box-disk-2'), 'vagrant-test_default_1.img'],
            [double('box-disk-3'), 'vagrant-test_default_2.img'],
            [double('additional-disk-1'), 'vagrant-test_default-vdd.qcow2'],
            [double('additional-disk-2'), 'vagrant-test_default-vde.qcow2'],
          ]}

          before do
            allow(libvirt_domain).to receive(:xml_desc).and_return(domain_xml)
            allow(domain).to receive(:volumes).and_return(domain_disks.map { |a| a.first })
          end

          it 'destroys disks individually' do
            domain_disks.each do |disk, name|
              expect(disk).to receive(:name).and_return(name).at_least(:once)
              expect(disk).to receive(:destroy)
            end
            expect(domain).to receive(:destroy).with(destroy_volumes: false, flags: 0)
            expect(subject.call(env)).to be_nil
          end

          context 'when has disks added via custom virsh commands' do
            let(:domain_xml_file) { 'box_multiple_disks_and_additional_and_custom_disks.xml' }
            let(:domain_disks) {[
              [double('box-disk-1'), 'vagrant-test_default.img'],
              [double('box-disk-2'), 'vagrant-test_default_1.img'],
              [double('box-disk-3'), 'vagrant-test_default_2.img'],
              [double('additional-disk-1'), 'vagrant-test_default-vdd.qcow2'],
              [double('additional-disk-2'), 'vagrant-test_default-vde.qcow2'],
              [double('custom-disk-1'), 'vagrant-test_default-vdf.qcow2'],
            ]}

            it 'only destroys expected disks' do
              expect(ui).to receive(:warn).with(/Unexpected number of volumes detected.*/)
              domain_disks.each do |disk, name|
                expect(disk).to receive(:name).and_return(name).at_least(:once)
                next if disk == domain_disks.last.first
                expect(disk).to receive(:destroy)
              end
              expect(domain).to receive(:destroy).with(destroy_volumes: false, flags: 0)
              expect(subject.call(env)).to be_nil
            end

            context 'without aliases' do
              let(:domain_xml_file) { 'box_multiple_disks_and_additional_and_custom_disks_no_aliases.xml' }

              it 'only destroys expected disks' do
                expect(ui).to receive(:warn).with(/Machine that was originally created without device aliases.*/)
                expect(ui).to receive(:warn).with(/Unexpected number of volumes detected/)
                expect(ui).to receive(:warn).with(/box metadata not available to get volume list during destroy, assuming inferred list/)
                domain_disks.each do |disk, name|
                  expect(disk).to receive(:name).and_return(name).at_least(:once)
                  # ignore box disks 2 and 3 and the last custom disk
                  next if domain_disks.last.first == disk
                  expect(disk).to receive(:destroy)
                end
                expect(domain).to receive(:destroy).with(destroy_volumes: false, flags: 0)
                expect(subject.call(env)).to be_nil
              end

              context 'with box metadata' do
                let(:box) { instance_double(::Vagrant::Box) }
                before do
                  allow(env[:machine]).to receive(:box).and_return(box)
                  allow(box).to receive(:metadata).and_return(Hash[
                    'disks' => [
                      {:name => 'box-disk-1'},
                      {:name => 'box-disk-2'},
                      {:name => 'box-disk-3'},
                    ]
                  ])
                end

                it 'only destroys expected disks' do
                  expect(ui).to receive(:warn).with(/Machine that was originally created without device aliases.*/)
                  expect(ui).to receive(:warn).with(/Unexpected number of volumes detected/)
                  domain_disks.each do |disk, name|
                    expect(disk).to receive(:name).and_return(name).at_least(:once)
                    # ignore box disks 2 and 3 and the last custom disk
                    next if domain_disks.last.first == disk
                    expect(disk).to receive(:destroy)
                  end
                  expect(domain).to receive(:destroy).with(destroy_volumes: false, flags: 0)
                  expect(subject.call(env)).to be_nil
                end
              end
            end
          end
        end
      end

      context 'when has nvram' do
        let(:vagrantfile) do
          <<-EOF
          Vagrant.configure('2') do |config|
            config.vm.define :test
            config.vm.provider :libvirt do |libvirt|
              libvirt.nvram = "test"
            end
          end
          EOF
        end

        it 'sets destroy flags to keep nvram' do
          expect(domain).to receive(:destroy).with(destroy_volumes: true, flags: VagrantPlugins::ProviderLibvirt::Util::DomainFlags::VIR_DOMAIN_UNDEFINE_KEEP_NVRAM)
          expect(subject.call(env)).to be_nil
        end
      end

      context 'when has CDROMs attached' do
        let(:vagrantfile_providerconfig) do
          <<-EOF
              libvirt.storage :file, :device => :cdrom
          EOF
        end
        let(:domain_xml_file) { 'cdrom_domain.xml' }

        it 'uses explicit removal of disks' do
          expect(domain).to receive(:volumes).and_return([root_disk, nil])
          expect(libvirt_domain).to receive(:xml_desc).and_return(domain_xml)

          expect(domain).to_not receive(:destroy).with(destroy_volumes: true, flags: 0)
          expect(root_disk).to receive(:destroy)  # root disk remove
          expect(subject.call(env)).to be_nil
        end
      end
    end
  end
end
