#
# Copyright:: Copyright (c) 2015 Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

require 'spec_helper'
require 'chef-dk/helpers'
require 'chef-dk/policyfile_services/update_attributes'

describe ChefDK::PolicyfileServices::UpdateAttributes do

  include ChefDK::Helpers

  let(:working_dir) do
    path = File.join(tempdir, "policyfile_services_test_working_dir")
    Dir.mkdir(path)
    path
  end

  let(:policyfile_rb_explicit_name) { nil }

  let(:policyfile_rb_name) { policyfile_rb_explicit_name || "Policyfile.rb" }

  let(:policyfile_lock_name) { "Policyfile.lock.json" }

  let(:policyfile_rb_path) { File.join(working_dir, policyfile_rb_name) }

  let(:policyfile_lock_path) { File.join(working_dir, policyfile_lock_name) }

  let(:local_cookbooks_root) do
    File.join(fixtures_path, "local_path_cookbooks")
  end

  let(:local_cookbook_path) { File.join(local_cookbooks_root, "local-cookbook") }

  let(:local_cookbook_copy_path) { File.join(working_dir, "cookbooks/local-cookbook") }

  let(:policyfile_content) do
    <<-E
name 'install-example'

run_list 'local-cookbook'

cookbook 'local-cookbook', path: 'cookbooks/local-cookbook'

default["default_attr"] = "new_value_default"

override["override_attr"] = "new_value_override"
E
  end

  let(:ui) { TestHelpers::TestUI.new }

  let(:storage_config) do
    ChefDK::Policyfile::StorageConfig.new( cache_path: nil, relative_paths_root: working_dir )
  end

  subject(:update_attrs_service) { described_class.new(policyfile: policyfile_rb_name, ui: ui, root_dir: working_dir) }

  before do
    FileUtils.mkdir_p(File.dirname(local_cookbook_copy_path))
    FileUtils.cp_r(local_cookbook_path, local_cookbook_copy_path)
  end

  context "when first created" do

    it "has the UI object it was created with" do
      expect(update_attrs_service.ui).to eq(ui)
    end

    it "creates a storage config from the given policyfile path and root dir" do
      new_storage_config = instance_double("ChefDK::Policyfile::StorageConfig")
      expect(ChefDK::Policyfile::StorageConfig).to receive(:new).with(no_args).and_return(new_storage_config)
      expect(new_storage_config).to receive(:use_policyfile).with(policyfile_rb_path).and_return(new_storage_config)
      expect(update_attrs_service.storage_config).to eq(new_storage_config)
    end

  end

  context "when no Policyfile is present or specified" do

    it "errors out" do
      expect { update_attrs_service.assert_policy_and_lock_present! }.to raise_error(ChefDK::PolicyfileNotFound, "Policyfile not found at path #{policyfile_rb_path}")
      expect { update_attrs_service.run }.to raise_error(ChefDK::PolicyfileUpdateError)
    end

  end

  context "when no lockfile exists" do

    it "errors out" do
      with_file(policyfile_rb_path) { |f| f.print(policyfile_content) }
      expect { update_attrs_service.assert_policy_and_lock_present! }.to raise_error(ChefDK::LockfileNotFound, "Policyfile lock not found at path #{policyfile_lock_path}")
      expect { update_attrs_service.run }.to raise_error(ChefDK::PolicyfileUpdateError)
    end

  end

  context "when both the policyfile and lockfile exist" do

    let(:lock_data_with_new_values) do
      {
        "revision_id" => "522d740beba4c4e5857bd8bccdb2d7ffd0bbd45ac4350f92b26e4e3b8f68d530",
        "name" => "install-example",
        "run_list" => ["recipe[local-cookbook::default]"],
        "cookbook_locks"=> {
          "local-cookbook"=> {
            "version"=>"2.3.4",
            "identifier"=>"fab501cfaf747901bd82c1bc706beae7dc3a350c",
            "dotted_decimal_identifier"=> "70567763561641081.489844270461035.258281553147148",
            "source"=>"cookbooks/local-cookbook",
            "cache_key"=>nil,
            "scm_info" => nil,
            "source_options"=> {
              "path"=>"cookbooks/local-cookbook"
            }
          }
        },
        "default_attributes" => {"default_attr"=>"new_value_default"},
        "override_attributes" => {"override_attr"=>"new_value_override"},
        "solution_dependencies" => {
          "Policyfile"=>[["local-cookbook", ">= 0.0.0"]],
          "dependencies"=>{"local-cookbook (2.3.4)"=>[]}
        }
      }
    end

    let(:previous_policyfile_lock_data) { lock_data_with_new_values }

    let(:policyfile_lock_content) do
      FFI_Yajl::Encoder.encode(previous_policyfile_lock_data, pretty: true )
    end

    before do
      with_file(policyfile_rb_path) { |f| f.print(policyfile_content) }
      with_file(policyfile_lock_path) { |f| f.print(policyfile_lock_content) }
    end

    def result_policyfile_lock_data
      expect(File).to exist(policyfile_lock_path)
      content = IO.read(policyfile_lock_path)
      FFI_Yajl::Parser.parse(content)
    end

    context "when the current lock already has the desired attributes" do

      it "makes no changes to the lockfile" do
        update_attrs_service.run
        expect(result_policyfile_lock_data).to eq(lock_data_with_new_values)
      end

      it "emits a message that no changes have been made to the lockfile" do
        update_attrs_service.run

        message = "Attributes already up to date"
        expect(ui.output).to include(message)
      end

    end

    context "when the Policyfile.rb has different attributes than the lockfile" do

      let(:previous_policyfile_lock_data) do
        {
          "revision_id" => "522d740beba4c4e5857bd8bccdb2d7ffd0bbd45ac4350f92b26e4e3b8f68d530",
          "name" => "install-example",
          "run_list" => ["recipe[local-cookbook::default]"],
          "cookbook_locks"=> {
            "local-cookbook"=> {
              "version"=>"2.3.4",
              "identifier"=>"fab501cfaf747901bd82c1bc706beae7dc3a350c",
              "dotted_decimal_identifier"=> "70567763561641081.489844270461035.258281553147148",
              "source"=>"cookbooks/local-cookbook",
              "cache_key" => nil,
              "scm_info" => nil,
              "source_options"=> {
                "path"=>"cookbooks/local-cookbook"
              }
            }
          },
          "default_attributes" => {"default_attr"=>"old_value_default"},
          "override_attributes" => {"override_attr"=>"old_value_override"},
          "solution_dependencies" => {
            "Policyfile"=>[["local-cookbook", ">= 0.0.0"]],
            "dependencies"=>{"local-cookbook (2.3.4)"=>[]}
          }
        }
      end

      it "updates the lockfile with the new attributes" do
        update_attrs_service.run
        expect(result_policyfile_lock_data).to eq(lock_data_with_new_values)
      end

      it "emits a messsage stating the attributes have been updated" do
        update_attrs_service.run
        expect(ui.output).to include("Updated attributes in #{policyfile_lock_path}")
      end

    end

  end

end

