require "pathname"
require "tmpdir"

require File.expand_path("../../../../../base", __FILE__)

require Vagrant.source_root.join("plugins/commands/box/command/update")

describe VagrantPlugins::CommandBox::Command::Update do
  include_context "unit"

  let(:argv)     { [] }
  let(:iso_env) do
    # We have to create a Vagrantfile so there is a root path
    test_iso_env.vagrantfile("")
    test_iso_env.create_vagrant_env
  end
  let(:test_iso_env) { isolated_environment }

  let(:action_runner) { double("action_runner") }
  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }

  subject { described_class.new(argv, iso_env) }

  before do
    iso_env.stub(action_runner: action_runner)
  end

  describe "execute" do
    context "updating specific box" do
      let(:argv) { ["--box", "foo"] }

      let(:metadata_url) { Pathname.new(Dir.mktmpdir).join("metadata.json") }

      before do
        metadata_url.open("w") do |f|
          f.write("")
        end

        test_iso_env.box3(
          "foo", "1.0", :virtualbox, metadata_url: metadata_url.to_s)
      end

      it "doesn't update if they're up to date" do
        called = false
        allow(action_runner).to receive(:run) do |callable, opts|
          if opts[:box_provider]
            called = true
          end

          opts
        end

        subject.execute

        expect(called).to be_false
      end

      it "does update if there is an update" do
        metadata_url.open("w") do |f|
          f.write(<<-RAW)
      {
        "name": "foo",
        "versions": [
          {
            "version": "1.0"
          },
          {
            "version": "1.1",
            "providers": [
              {
                "name": "virtualbox",
                "url": "bar"
              }
            ]
          }
        ]
      }
          RAW
        end

        action_called = false
        allow(action_runner).to receive(:run) do |action, opts|
          if opts[:box_provider]
            action_called = true
            expect(opts[:box_url]).to eq(metadata_url.to_s)
            expect(opts[:box_provider]).to eq("virtualbox")
            expect(opts[:box_version]).to eq("1.1")
          end

          opts
        end

        subject.execute

        expect(action_called).to be_true
      end

      it "raises an error if there are multiple providers" do
        test_iso_env.box3("foo", "1.0", :vmware)

        expect(action_runner).to receive(:run).never

        expect { subject.execute }.
          to raise_error(Vagrant::Errors::BoxUpdateMultiProvider)
      end

      context "with multiple providers and specifying the provider" do
        let(:argv) { ["--box", "foo", "--provider", "vmware"] }

        it "updates the proper box" do
          metadata_url.open("w") do |f|
            f.write(<<-RAW)
      {
        "name": "foo",
        "versions": [
          {
            "version": "1.0"
          },
          {
            "version": "1.1",
            "providers": [
              {
                "name": "vmware",
                "url": "bar"
              }
            ]
          }
        ]
      }
            RAW
          end

          test_iso_env.box3("foo", "1.0", :vmware)

          action_called = false
          allow(action_runner).to receive(:run) do |action, opts|
            if opts[:box_provider]
              action_called = true
              expect(opts[:box_url]).to eq(metadata_url.to_s)
              expect(opts[:box_provider]).to eq("vmware")
              expect(opts[:box_version]).to eq("1.1")
            end

            opts
          end

          subject.execute

          expect(action_called).to be_true
        end

        it "raises an error if that provider doesn't exist" do
          expect(action_runner).to receive(:run).never

          expect { subject.execute }.
            to raise_error(Vagrant::Errors::BoxNotFoundWithProvider)
        end
      end

      context "with a box that doesn't exist" do
        let(:argv) { ["--box", "nope"] }

        it "raises an exception" do
          expect(action_runner).to receive(:run).never

          expect { subject.execute }.
            to raise_error(Vagrant::Errors::BoxNotFound)
        end
      end
    end

    context "updating environment machines" do
      before do
        allow(subject).to receive(:with_target_vms) { |&block| block.call machine }
      end

      let(:box) do
        box_dir = test_iso_env.box3("foo", "1.0", :virtualbox)
        box = Vagrant::Box.new(
          "foo", :virtualbox, "1.0", box_dir, metadata_url: "foo")
        box.stub(has_update?: nil)
        box
      end

      it "ignores machines without boxes" do
        expect(action_runner).to receive(:run).never

        subject.execute
      end

      it "doesn't update boxes if they're up-to-date" do
        machine.stub(box: box)
        expect(box).to receive(:has_update?).
          with(machine.config.vm.box_version).
          and_return(nil)

        expect(action_runner).to receive(:run).never

        subject.execute
      end

      it "updates boxes if they have an update" do
        md = Vagrant::BoxMetadata.new(StringIO.new(<<-RAW))
      {
        "name": "foo",
        "versions": [
          {
            "version": "1.0"
          },
          {
            "version": "1.1",
            "providers": [
              {
                "name": "virtualbox",
                "url": "bar"
              }
            ]
          }
        ]
      }
        RAW

        machine.stub(box: box)
        expect(box).to receive(:has_update?).
          with(machine.config.vm.box_version).
          and_return([md, md.version("1.1"), md.version("1.1").provider("virtualbox")])

        expect(action_runner).to receive(:run).with { |action, opts|
          expect(opts[:box_url]).to eq(box.metadata_url)
          expect(opts[:box_provider]).to eq("virtualbox")
          expect(opts[:box_version]).to eq("1.1")
          expect(opts[:ui]).to equal(machine.ui)
          true
        }

        subject.execute
      end
    end
  end
end
