require 'spec_helper'

module Spree
  module Stock
    describe Package do
      let(:variant) { build(:variant, weight: 25.0) }
      let(:stock_location) { build(:stock_location) }
      let(:order) { build(:order) }

      subject { Package.new(stock_location) }

      def build_inventory_unit
        build(:inventory_unit, variant: variant)
      end

      it 'calculates the weight of all the contents' do
        4.times { subject.add build_inventory_unit }
        subject.weight.should == 100.0
      end

      it 'filters by on_hand and backordered' do
        4.times { subject.add build_inventory_unit }
        3.times { subject.add build_inventory_unit, :backordered }
        subject.on_hand.count.should eq 4
        subject.backordered.count.should eq 3
      end

      it 'calculates the quantity by state' do
        4.times { subject.add build_inventory_unit }
        3.times { subject.add build_inventory_unit, :backordered }

        subject.quantity.should eq 7
        subject.quantity(:on_hand).should eq 4
        subject.quantity(:backordered).should eq 3
      end

      it 'returns nil for content item not found' do
        unit = build_inventory_unit
        item = subject.find_item(unit, :on_hand)
        item.should be_nil
      end

      it 'finds content item for an inventory unit' do
        unit = build_inventory_unit
        subject.add unit
        item = subject.find_item(unit, :on_hand)
        item.quantity.should eq 1
      end

      # Contains regression test for #2804
      it 'builds a list of shipping methods common to all categories' do
        category1 = create(:shipping_category)
        category2 = create(:shipping_category)
        method1   = create(:shipping_method)
        method2   = create(:shipping_method)
        method1.shipping_categories = [category1, category2]
        method2.shipping_categories = [category1]
        variant1 = mock_model(Variant, shipping_category: category1)
        variant2 = mock_model(Variant, shipping_category: category2)
        variant3 = mock_model(Variant, shipping_category: nil)
        contents = [ContentItem.new(build(:inventory_unit, variant: variant1)),
                    ContentItem.new(build(:inventory_unit, variant: variant1)),
                    ContentItem.new(build(:inventory_unit, variant: variant2)),
                    ContentItem.new(build(:inventory_unit, variant: variant3))]

        package = Package.new(stock_location, contents)
        package.shipping_methods.should == [method1]
      end

      it 'builds an empty list of shipping methods when no categories' do
        variant  = mock_model(Variant, shipping_category: nil)
        contents = [ContentItem.new(build(:inventory_unit, variant: variant))]
        package  = Package.new(stock_location, contents)
        package.shipping_methods.should be_empty
      end

      it "can convert to a shipment" do
        2.times { subject.add build_inventory_unit }
        subject.add build_inventory_unit, :backordered

        shipping_method = build(:shipping_method)
        subject.shipping_rates = [ Spree::ShippingRate.new(shipping_method: shipping_method, cost: 10.00, selected: true) ]

        shipment = subject.to_shipment
        expect(shipment.stock_location).to eq subject.stock_location
        expect(shipment.inventory_units.size).to eq 3

        first_unit = shipment.inventory_units.first
        expect(first_unit.variant).to eq variant
        expect(first_unit.state).to eq 'on_hand'
        expect(first_unit).to be_pending

        last_unit = shipment.inventory_units.last
        expect(last_unit.variant).to eq variant
        expect(last_unit.state).to eq 'backordered'

        expect(shipment.shipping_method).to eq shipping_method
      end

      it 'does not add an inventory unit to a package twice' do
        # since inventory units currently don't have a quantity
        unit = build_inventory_unit
        subject.add unit
        subject.add unit
        expect(subject.quantity).to eq 1
        expect(subject.contents.first.inventory_unit).to eq unit
        expect(subject.contents.first.quantity).to eq 1
      end

      describe "#add_multiple" do
        it "adds multiple inventory units" do
          expect { subject.add_multiple [build_inventory_unit, build_inventory_unit] }.to change { subject.quantity }.by(2)
        end

        it "allows adding with a state" do
          expect { subject.add_multiple [build_inventory_unit, build_inventory_unit], :backordered }.to change { subject.backordered.count }.by(2)
        end

        it "defaults to adding with the on hand state" do
          expect { subject.add_multiple [build_inventory_unit, build_inventory_unit] }.to change { subject.on_hand.count }.by(2)
        end
      end

      describe "#remove" do
        let(:unit) { build_inventory_unit }
        context "there is a content item for the inventory unit" do

          before { subject.add unit }

          it "removes that content item" do
            expect { subject.remove(unit) }.to change { subject.quantity }.by(-1)
            expect(subject.contents.map(&:inventory_unit)).not_to include unit
          end
        end

        context "there is no content item for the inventory unit" do
          it "doesn't change the set of content items" do
            expect { subject.remove(unit) }.not_to change { subject.quantity }
          end
        end
      end
    end
  end
end
