require File.join(File.dirname(__FILE__), '../../test_helper')
require 'yaml'
describe HammerCLI::Output::Adapter::Yaml do

  let(:context) {{}}
  let(:adapter) { HammerCLI::Output::Adapter::Yaml.new(context, HammerCLI::Output::Output.formatters) }

  it "forbids default pagination" do
    adapter.paginate_by_default?.must_equal false
  end

  context "print_message" do
    it "prints the message" do
      params = { :a => 'Test', :b => 83 }
      msg = "Rendered with %{a} and %{b}"
      expected_output = [
        '---',
        ':message: Rendered with Test and 83',
        ''
      ].join("\n")

      proc { adapter.print_message(msg, params) }.must_output(expected_output)
    end

    it "prints the message with name and id" do
      params = { :name => 'Test', :id => 83 }
      msg = "Rendered with %{name} and %{id}"
      expected_output = [
        '---',
        ':message: Rendered with Test and 83',
        ':id: 83',
        ':name: Test',
        ''
      ].join("\n")

      proc { adapter.print_message(msg, params) }.must_output(expected_output)
    end

    it 'prints the message with nil params' do
      params = nil
      msg = 'MESSAGE'
      expected_output = [
        '---',
        ':message: MESSAGE',
        ''
      ].join("\n")
      proc { adapter.print_message(msg, params) }.must_output(expected_output)
    end
  end

  context "print_collection" do

    let(:id)            { Fields::Id.new(:path => [:id], :label => "Id") }
    let(:name)          { Fields::Field.new(:path => [:name], :label => "Name") }
    let(:unlabeled)     { Fields::Field.new(:path => [:name]) }
    let(:surname)       { Fields::Field.new(:path => [:surname], :label => "Surname") }
    let(:address_city)  { Fields::Field.new(:path => [:address, :city], :label => "City") }
    let(:city)          { Fields::Field.new(:path => [:city], :label => "City") }
    let(:label_address) { Fields::Label.new(:path => [:address], :label => "Address") }
    let(:num_contacts)  { Fields::Collection.new(:path => [:contacts], :label => "Contacts") }
    let(:num_one_contact)  { Fields::Collection.new(:path => [:one_contact], :label => "Contacts") }
    let(:contacts)      { Fields::Collection.new(:path => [:contacts], :label => "Contacts", :numbered => false) }
    let(:one_contact)      { Fields::Collection.new(:path => [:one_contact], :label => "Contacts", :numbered => false) }
    let(:desc)          { Fields::Field.new(:path => [:desc], :label => "Description") }
    let(:contact)       { Fields::Field.new(:path => [:contact], :label => "Contact") }
    let(:params)        { Fields::KeyValueList.new(:path => [:params], :label => "Parameters") }
    let(:params_collection) { Fields::Collection.new(:path => [:params], :label => "Parameters") }
    let(:param)             { Fields::KeyValue.new(:path => nil, :label => nil) }
    let(:blank)             { Fields::Field.new(:path => [:blank], :label => "Blank", :hide_blank => true) }
    let(:login)           { Fields::Field.new(:path => [:login], :label => "Login") }
    let(:missing)           { Fields::Field.new(:path => [:missing], :label => "Missing", :hide_missing => false) }

    let(:data) { HammerCLI::Output::RecordCollection.new [{
      :id => 112,
      :name => "John",
      :surname => "Doe",
      :address => {
        :city => "New York"
      },
      :contacts => [
        {
          :desc => 'personal email',
          :contact => 'john.doe@doughnut.com'
        },
        {
          :desc => 'telephone',
          :contact => '123456789'
        }
      ],
      :one_contact => [
        {
          :desc => 'personal email',
          :contact => 'john.doe@doughnut.com'
        }
      ],
      :params => [
        {
          :name => 'weight',
          :value => '83'
        },
        {
          :name => 'size',
          :value => '32'
        }
      ]
    }]}

    it "should print one field" do
      fields = [name]
      expected_output = YAML.dump([{ 'Name' => 'John' }])
      proc { adapter.print_collection(fields, data) }.must_output(expected_output)
    end

    it "should field with nested data" do
      fields = [address_city]
      expected_output = YAML.dump([{ 'City' => 'New York' }])
      proc { adapter.print_collection(fields, data) }.must_output(expected_output)
    end

    it "should print labeled fields" do
      label_address.output_definition.append [city]
      fields = [label_address]
      hash = [{
                'Address' => {
                  'City' => 'New York'
                }
              }]
      expected_output = YAML.dump(hash)
      proc { adapter.print_collection(fields, data) }.must_output(expected_output)
    end

    it "should print collection" do
      num_contacts.output_definition.append [desc, contact]
      fields = [num_contacts]
      hash = [{
                'Contacts' => {
                  1 => {
                    'Description' => 'personal email',
                    'Contact' => 'john.doe@doughnut.com'
                  },
                  2 => {
                    'Description' => 'telephone',
                    'Contact' => '123456789'
                  }
                }
              }]

      expected_output = YAML.dump(hash)
      proc { adapter.print_collection(fields, data) }.must_output(expected_output)
    end

    it "should print collection with one element" do
      num_one_contact.output_definition.append [desc, contact]
      fields = [num_one_contact]
      hash = [{
                'Contacts' => {
                  1 => {
                    'Description' => 'personal email',
                    'Contact' => 'john.doe@doughnut.com'
                  }
                }
              }]

      expected_output = YAML.dump(hash)
      proc { adapter.print_collection(fields, data) }.must_output(expected_output)
    end

    it "should print unnumbered collection" do
      contacts.output_definition.append [desc, contact]
      fields = [contacts]
      hash = [{
                'Contacts' => [
                               {
                                 'Description' => 'personal email',
                                 'Contact' => 'john.doe@doughnut.com'
                               },
                               {
                                 'Description' => 'telephone',
                                 'Contact' => '123456789'
                               }]
              }]

      expected_output = YAML.dump(hash)
      proc { adapter.print_collection(fields, data) }.must_output(expected_output)
    end

    it "should print unnumbered collection with one element" do
      one_contact.output_definition.append [desc, contact]
      fields = [one_contact]
      hash = [{
                'Contacts' => [
                               {
                                 'Description' => 'personal email',
                                 'Contact' => 'john.doe@doughnut.com'
                               }]
              }]

      expected_output = YAML.dump(hash)
      proc { adapter.print_collection(fields, data) }.must_output(expected_output)
    end


    it "hides ids by default" do
      fields = [id, name]
      hash = [{'Name' => 'John'}]
      expected_output = YAML.dump(hash)

      proc { adapter.print_collection(fields, data) }.must_output(expected_output)
    end

    it "skips blank values" do
      fields = [name, blank]
      hash = [{'Name' => 'John'}]
      expected_output = YAML.dump(hash)

      proc { adapter.print_collection(fields, data) }.must_output(expected_output)
    end

    it "does not print fields which data are missing from api by default" do
      fields = [surname, login]
      hash = [{ 'Surname' => 'Doe' }]
      expected_output = YAML.dump(hash)

      proc { adapter.print_collection(fields, data) }.must_output(expected_output)
    end

    it "prints fields which data are missing from api when field has hide_missing flag set to false" do
      fields = [surname, missing]
      hash = [{ 'Surname' => 'Doe', 'Missing' => HammerCLI::Output::DataMissing.new }]
      expected_output = YAML.dump(hash)

      proc { adapter.print_collection(fields, data) }.must_output(expected_output)
    end

    it "should print key -> value" do
      params_collection.output_definition.append [param]
      fields = [params_collection]

      hash = [{
                'Parameters' => {
                  1 => {
                    :name => 'weight',
                    :value => '83'
                  },
                  2 => {
                    :name => 'size',
                    :value => '32'
                  }
                }
              }]
      expected_output = YAML.dump(hash)

      proc { adapter.print_collection(fields, data) }.must_output(expected_output)
    end

    context 'capitalization' do
      let(:fields)   { [name, surname] }
      let(:raw_hash) { { 'Name' => 'John', 'Surname' => 'Doe' } }
      let(:settings) { HammerCLI::Settings }
      let(:context) { { :capitalization => HammerCLI.capitalization } }

      it 'should respect selected downcase capitalization' do
        settings.load({ :ui => { :capitalization => :downcase } })
        hash = [raw_hash.transform_keys(&:downcase)]
        expected_output = YAML.dump(hash)
        out, = capture_io do
          adapter.print_collection(fields, data)
        end
        out.must_equal(expected_output)
      end

      it 'should respect selected capitalize capitalization' do
        settings.load({ :ui => { :capitalization => :capitalize } })
        hash = [raw_hash.transform_keys(&:capitalize)]
        expected_output = YAML.dump(hash)
        out, = capture_io do
          adapter.print_collection(fields, data)
        end
        out.must_equal(expected_output)
      end

      it 'should respect selected upcase capitalization' do
        settings.load({ :ui => { :capitalization => :upcase } })
        hash = [raw_hash.transform_keys(&:upcase)]
        expected_output = YAML.dump(hash)
        out, = capture_io do
          adapter.print_collection(fields, data)
        end
        out.must_equal(expected_output)
      end

      it 'should print a warn for not supported capitalization' do
        settings.load({ :ui => { :capitalization => :unsupported } })
        hash = [raw_hash]
        expected_error = "Cannot use such capitalization. Try one of downcase, capitalize, upcase.\n"
        expected_output = YAML.dump(hash)
        out, err = capture_io do
          adapter.print_collection(fields, data)
        end
        out.must_equal(expected_output)
        err.must_equal(expected_error)
      end

      it "shouldn't change capitalization if wasn't selected" do
        settings.load({ :ui => { :capitalization => nil } })
        hash = [raw_hash]
        expected_output = YAML.dump(hash)
        out, = capture_io do
          adapter.print_collection(fields, data)
        end
        out.must_equal(expected_output)
      end
    end

    context 'printing by chunks' do
      let(:context) { { show_ids: true } }
      let(:collection_count) { 30 }
      let(:collection) do
        collection_count.times.each_with_object([]) do |t, r|
          r << { id: t, name: "John #{t}"}
        end
      end
      let(:prepared_collection) do
        collection.map { |i| i.transform_keys { |k| k.to_s.capitalize } }
      end
      let(:collection_data) do
        HammerCLI::Output::RecordCollection.new(collection)
      end
      let(:fields) { [id, name] }

      it 'prints single chunk' do
        expected_output = YAML.dump(prepared_collection)

        out, _err = capture_io do
          adapter.print_collection(fields, collection_data)
        end
        out.must_equal(expected_output)
      end

      it 'prints first chunk' do
        expected_output = YAML.dump(prepared_collection[0...10])

        out, _err = capture_io do
          adapter.print_collection(
            fields, collection_data[0...10], current_chunk: :first
          )
        end
        out.must_equal(expected_output)
      end

      it 'prints another chunk' do
        expected_output = YAML.dump(prepared_collection[10...20])[4..-1]

        out, _err = capture_io do
          adapter.print_collection(
            fields, collection_data[10...20], current_chunk: :another
          )
        end
        out.must_equal(expected_output)
      end

      it 'prints last chunk' do
        expected_output = YAML.dump(prepared_collection[20...30])[4..-1]

        out, _err = capture_io do
          adapter.print_collection(
            fields, collection_data[20...30], current_chunk: :last
          )
        end
        out.must_equal(expected_output)
      end
    end

    context "show ids" do

      let(:context) { {:show_ids => true} }

      it "shows ids if it's required in the context" do
        fields = [id, name]
        hash = [{
                  'Id' => 112,
                  'Name' => 'John'
                }]
        expected_output = YAML.dump(hash)
        proc { adapter.print_collection(fields, data) }.must_output(expected_output)
      end

    end

    context "output_stream" do

      let(:tempfile) { Tempfile.new("output_stream_yaml_test_temp") }
      let(:context) { {:output_file => tempfile} }

      it "should not print to stdout when --output-file is set" do
        fields = [name]

        proc { adapter.print_collection(fields, data) }.must_output("")
      end

      it "should print to file if --output-file is set" do
        fields = [name]
        hash = [{
                  'Name' => 'John'
                }]
        expected_output = YAML.dump(hash)

        adapter.print_collection(fields, data)
        tempfile.close
        IO.read(tempfile.path).must_equal(expected_output)
      end

    end

  end

end