require "kontena/cli/stacks/upgrade_command"
require 'json'

describe Kontena::Cli::Stacks::UpgradeCommand do

  include ClientHelpers
  include RequirementsHelper
  include FixturesHelpers

  mock_current_master

  before(:each) do
    ENV['STACK'] = nil
    allow(client).to receive(:server_version).and_return(Kontena::Cli::VERSION)
  end

  describe '#execute' do

    let(:stack_expectation) do
      {
        'name' => 'stack-name',
        'stack' => 'user/stackname',
        'version' => '0.1.1',
        'registry' => 'file://',
        'services' => array_including(hash_including('name', 'image')),
        'variables' => {},
        'volumes' => [],
        'dependencies' => nil,
        'source' => /stack:/,
        'expose' => nil
      }
    end

    let(:stack_response) do
      stack_expectation.merge('services' => [{'name' => 'foo', 'image' => 'bar'}], 'source' => 'foo')
    end

    before(:each) do
      allow(File).to receive(:read).with('kontena.yml').and_return(fixture('kontena_v3.yml'))
      allow(File).to receive(:exist?).with('kontena.yml').and_return(true)
      allow(subject.loader_class).to receive(:for).and_call_original
    end

    expect_to_require_current_master
    expect_to_require_current_master_token

    it 'uses kontena.yml as default stack file' do
      expect(subject.instance(['stack-name']).source).to eq 'kontena.yml'
      expect(subject.instance(['stack-name']).stack_name).to eq 'stack-name'
    end

    it 'sends stack to master' do
      expect(client).to receive(:get).with('stacks/test-grid/stack-a').and_return(stack_response)
      expect(client).to receive(:put).with('stacks/test-grid/stack-a', hash_including(stack_expectation.merge('name' => 'stack-a'))).and_return(true)
      subject.run(['--no-deploy', '--force', 'stack-a', fixture_path('kontena_v3.yml')])
    end

    it 'requires confirmation when master stack is different than input stack' do
      expect(client).to receive(:get).with('stacks/test-grid/stack-b').and_return(stack_response.merge('stack' => 'foo/otherstack'))
      expect(subject).to receive(:confirm).and_call_original
      expect{subject.run(['stack-b', fixture_path('kontena_v3.yml')])}.to exit_with_error.and output(/- stack-b from foo\/otherstack to user\/stackname/).to_stdout
    end

    it 'triggers deploy by default' do
      expect(client).to receive(:get).with('stacks/test-grid/stack-a').and_return(stack_response)
      allow(client).to receive(:put).with(
        'stacks/test-grid/stack-a', anything
      ).and_return({})
      expect(Kontena).to receive(:run!).with(['stack', 'deploy', 'stack-a']).once
      subject.run(['--force', 'stack-a', fixture_path('kontena_v3.yml')])
    end

    it 'requires force if master version does not match metadata required_kontena_version' do
      expect(client).to receive(:get).with('stacks/test-grid/stack-a').and_return(stack_response)
      expect(client).to receive(:server_version).and_return('0.2.0')
      expect(subject).to receive(:confirm).and_call_original
      expect{subject.run(['--no-deploy', 'stack-a', fixture_path('kontena_v3_with_metadata.yml')])}.to exit_with_error.and output(/version/).to_stdout
    end

    context '--no-deploy option' do
      it 'does not trigger deploy' do
        expect(client).to receive(:get).with('stacks/test-grid/stack-a').and_return(stack_response)
        allow(client).to receive(:put).with(
          'stacks/test-grid/stack-a', anything
        ).and_return({})
        expect(Kontena).not_to receive(:run!).with(['stack', 'deploy', 'stack-a'])
        subject.run(['--no-deploy', '--force', 'stack-a', fixture_path('kontena_v3.yml')])
      end
    end
    context 'with a stack including dependencies' do

      let(:expectation)     {{ 'name' => 'deptest', 'stack' => 'user/depstack1' }}
      let(:expectation_1)   {{ 'name' => 'deptest-dep_1', 'stack' => 'user/depstack1child1'}}
      let(:expectation_1_1) {{ 'name' => 'deptest-dep_1-dep_1', 'stack' => 'user/depstack1child1child1', 'services' => array_including(hash_including('image' => 'image:2')) }}
      let(:expectation_2)   {{ 'name' => 'deptest-dep_2', 'stack' => 'user/depstack1child2', 'services' => array_including(hash_including('image' => 'image:1')), 'variables' => hash_including('dep_var' => 1) }}

      let(:response)     { expectation.merge('parent' => nil, 'children' => [{'name' => 'deptest-dep_1'}, {'name' => 'deptest-dep_2'}], 'services' => [])  }
      let(:response_1)   { expectation_1.merge('parent' => { 'name' => 'deptest' }, 'children' => [{'name' => 'deptest-dep_1-dep_1'}], 'services' => []) }
      let(:response_1_1) { expectation_1_1.merge('parent' => { 'name' => 'deptest-dep_1' }, 'children' => [], 'services' => []) }
      let(:response_2)   { expectation_2.merge('parent' => { 'name' => 'deptest' }, 'children' => [], 'variables' => {}, 'services' => []) }

      before  do
        allow(subject).to receive(:fetch_master_data).with('deptest').and_return(response)
        allow(subject).to receive(:fetch_master_data).with('deptest-dep_1').and_return(response_1)
        allow(subject).to receive(:fetch_master_data).with('deptest-dep_1-dep_1').and_return(response_1_1)
        allow(subject).to receive(:fetch_master_data).with('deptest-dep_2').and_return(response_2)
      end

      it 'upgrades all dependencies' do
        expect(client).to receive(:put).with('stacks/test-grid/deptest-dep_2', hash_including(expectation_2)).and_return(true)
        expect(client).to receive(:put).with('stacks/test-grid/deptest-dep_1-dep_1', hash_including(expectation_1_1)).and_return(true)
        expect(client).to receive(:put).with('stacks/test-grid/deptest-dep_1', hash_including(expectation_1)).and_return(true)
        expect(client).to receive(:put).with('stacks/test-grid/deptest', hash_including(expectation)).and_return(true)
        subject.run(['--force', '--no-deploy', '-v', 'dep_2.dep_var=1', 'deptest', fixture_path('stack-with-dependencies.yml')])
      end

      context 'when a dependency has been removed' do
        it 'warns if a stack no longer in the dependency chain would be removed' do
          expect(subject).to receive(:confirm).and_call_original
          expect{subject.run(['--no-deploy', 'deptest', fixture_path('stack-with-dependencies-dep_2-removed.yml')])}.to exit_with_error.and output(/- deptest-dep_2.*data will be lost/m).to_stdout
        end
      end

      context 'when a dependency has been added' do
        it 'installs any new stacks in the dependency chain' do
          allow(client).to receive(:put).and_return(true)
          expect(Kontena).to receive(:run!).with(["stack", "install", "--name", "deptest-dep_3", "--no-deploy", "--parent-name", "deptest", fixture_path('stack-with-dependencies-dep-3.yml')]).and_return(true)
          subject.run(['--no-deploy', '--force', '-v', 'dep_2.dep_var=1', 'deptest', fixture_path('stack-with-dependencies-dep_3-added.yml')])
        end
      end
    end
  end
end
