require 'spec_helper'

describe Spree::Api::ShipmentsController, type: :controller do
  render_views
  let!(:shipment) { create(:shipment, inventory_units: [build(:inventory_unit, shipment: nil)]) }
  let!(:attributes) { [:id, :tracking, :tracking_url, :number, :cost, :shipped_at, :stock_location_name, :order_id, :shipping_rates, :shipping_methods] }

  before do
    stub_authentication!
  end

  let!(:resource_scoping) { { id: shipment.to_param, shipment: { order_id: shipment.order.to_param } } }

  context "as a non-admin" do
    it "cannot make a shipment ready" do
      api_put :ready
      assert_unauthorized!
    end

    it "cannot make a shipment shipped" do
      api_put :ship
      assert_unauthorized!
    end

    it "cannot remove order contents from shipment" do
      api_put :remove
      assert_unauthorized!
    end

    it "cannot add contents to the shipment" do
      api_put :add
      assert_unauthorized!
    end

    it "cannot update the shipment" do
      api_put :update
      assert_unauthorized!
    end
  end

  context "as an admin" do
    let!(:order) { shipment.order }
    let!(:stock_location) { create(:stock_location_with_items) }
    let!(:variant) { create(:variant) }

    sign_in_as_admin!

    # Start writing this spec a bit differently than before....
    describe 'POST #create' do
      let(:params) do
        {
          variant_id: stock_location.stock_items.first.variant.to_param,
          shipment: { order_id: order.number },
          stock_location_id: stock_location.to_param
        }
      end

      subject do
        api_post :create, params
      end

      [:variant_id, :stock_location_id].each do |field|
        context "when #{field} is missing" do
          before do
            params.delete(field)
          end

          it 'should return proper error' do
            subject
            expect(response.status).to eq(422)
            expect(json_response['exception']).to eq("param is missing or the value is empty: #{field}")
          end
        end
      end

      it 'should create a new shipment' do
        expect(subject).to be_ok
        expect(json_response).to have_attributes(attributes)
      end
    end

    it 'can update a shipment' do
      params = {
        shipment: {
          stock_location_id: stock_location.to_param
        }
      }

      api_put :update, params
      expect(response.status).to eq(200)
      expect(json_response['stock_location_name']).to eq(stock_location.name)
    end

    it "can make a shipment ready" do
      allow_any_instance_of(Spree::Order).to receive_messages(paid?: true, complete?: true)
      api_put :ready
      expect(json_response).to have_attributes(attributes)
      expect(json_response["state"]).to eq("ready")
      expect(shipment.reload.state).to eq("ready")
    end

    it "cannot make a shipment ready if the order is unpaid" do
      allow_any_instance_of(Spree::Order).to receive_messages(paid?: false)
      api_put :ready
      expect(json_response["error"]).to eq("Cannot ready shipment.")
      expect(response.status).to eq(422)
    end

    context 'for completed orders' do
      let(:order) { create :completed_order_with_totals }
      let!(:resource_scoping) { { id: order.shipments.first.to_param, shipment: { order_id: order.to_param } } }

      it 'adds a variant to a shipment' do
        api_put :add, { variant_id: variant.to_param, quantity: 2 }
        expect(response.status).to eq(200)
        expect(json_response['manifest'].detect { |h| h['variant']['id'] == variant.id }["quantity"]).to eq(2)
      end

      it 'removes a variant from a shipment' do
        order.contents.add(variant, 2)

        api_put :remove, { variant_id: variant.to_param, quantity: 1 }
        expect(response.status).to eq(200)
        expect(json_response['manifest'].detect { |h| h['variant']['id'] == variant.id }["quantity"]).to eq(1)
      end

      it 'removes a destroyed variant from a shipment' do
        order.contents.add(variant, 2)
        variant.destroy

        api_put :remove, { variant_id: variant.to_param, quantity: 1 }
        expect(response.status).to eq(200)
        expect(json_response['manifest'].detect { |h| h['variant']['id'] == variant.id }["quantity"]).to eq(1)
      end
    end

    context "for shipped shipments" do
      let(:order) { create :shipped_order }
      let!(:resource_scoping) { { id: order.shipments.first.to_param, shipment: { order_id: order.to_param } } }

      it 'adds a variant to a shipment' do
        api_put :add, { variant_id: variant.to_param, quantity: 2 }
        expect(response.status).to eq(200)
        expect(json_response['manifest'].detect { |h| h['variant']['id'] == variant.id }["quantity"]).to eq(2)
      end

      it 'cannot remove a variant from a shipment' do
        api_put :remove, { variant_id: variant.to_param, quantity: 1 }
        expect(response.status).to eq(422)
        expect(json_response['errors']['base'].join).to match /Cannot remove items/
      end
    end

    describe '#mine' do
      subject do
        api_get :mine, params
      end

      let(:params) { {} }

      context "the current api user is authenticated and has orders" do
        let(:current_api_user) { shipped_order.user }
        let(:shipped_order) { create(:shipped_order) }

        it 'succeeds' do
          subject
          expect(response.status).to eq 200
        end

        describe 'json output' do
          render_views

          let(:rendered_shipment_ids) { json_response['shipments'].map { |s| s['id'] } }

          it 'contains the shipments' do
            subject
            expect(rendered_shipment_ids).to match_array current_api_user.orders.flat_map(&:shipments).map(&:id)
          end

          context "credit card payment" do
            before { subject }

            it 'contains the id and cc_type of the credit card' do
              expect(json_response['shipments'][0]['order']['payments'][0]['source'].keys).to match_array ["id", "cc_type"]
            end
          end

          context "store credit payment" do
            let(:current_api_user) { shipped_order.user }
            let(:shipped_order)    { create(:shipped_order, payment_type: :store_credit_payment) }

            before { subject }

            it 'only contains the id of the payment source' do
              expect(json_response['shipments'][0]['order']['payments'][0]['source'].keys).to match_array ["id"]
            end
          end
        end

        context 'with filtering' do
          let(:params) { { q: { order_completed_at_not_null: 1 } } }

          let!(:incomplete_order) { create(:order_with_line_items, user: current_api_user) }

          it 'filters' do
            subject
            expect(assigns(:shipments).map(&:id)).to match_array current_api_user.orders.complete.flat_map(&:shipments).map(&:id)
          end
        end
      end

      context "the current api user does not exist" do
        let(:current_api_user) { nil }

        it "returns a 401" do
          subject
          expect(response.status).to eq(401)
        end
      end
    end
  end

  describe "#ship" do
    let(:shipment) { create(:order_ready_to_ship).shipments.first }

    let(:send_mailer) { nil }
    subject { api_put :ship, id: shipment.to_param, send_mailer: send_mailer }

    context "the user is allowed to ship the shipment" do
      sign_in_as_admin!
      it "ships the shipment" do
        Timecop.freeze do
          subject
          shipment.reload
          expect(shipment.state).to eq 'shipped'
          expect(shipment.shipped_at.to_i).to eq Time.current.to_i
        end
      end

      context "send_mailer not present" do
        it "sends the shipped shipments mailer" do
          expect { subject }.to change { ActionMailer::Base.deliveries.size }.by(1)
          expect(ActionMailer::Base.deliveries.last.subject).to match /Shipment Notification/
        end
      end

      context "send_mailer set to false" do
        let(:send_mailer) { 'false' }
        it "does not send the shipped shipments mailer" do
          expect { subject }.to_not change { ActionMailer::Base.deliveries.size }
        end
      end

      context "send_mailer set to true" do
        let(:send_mailer) { 'true' }
        it "sends the shipped shipments mailer" do
          expect { subject }.to change { ActionMailer::Base.deliveries.size }.by(1)
          expect(ActionMailer::Base.deliveries.last.subject).to match /Shipment Notification/
        end
      end
    end

    context "the user is not allowed to ship the shipment" do
      sign_in_as_admin!

      before do
        ability = Spree::Ability.new(current_api_user)
        ability.cannot :ship, Spree::Shipment
        allow(controller).to receive(:current_ability) { ability }
      end

      it "does nothing" do
        expect {
          expect {
            subject
          }.not_to change(shipment, :state)
        }.not_to change(shipment, :shipped_at)
      end

      it "responds with a 401" do
        subject
        expect(response.status).to eq 401
      end
    end

    context "the user is not allowed to view the shipment" do
      it "does nothing" do
        expect {
          expect {
            subject
          }.not_to change(shipment, :state)
        }.not_to change(shipment, :shipped_at)
      end

      it "responds with a 401" do
        subject
        expect(response).to be_unauthorized
      end
    end
  end
end