# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Spree::Order do
  let(:order) { Spree::Order.new }
  before do
    # Ensure state machine has been re-defined correctly
    Spree::Order.define_state_machine!
    # We don't care about this validation here
    allow(order).to receive(:require_email)
  end

  context "#next!" do
    context "when current state is payment" do
      before do
        order.state = "payment"
        order.run_callbacks(:create)
        allow(order).to receive_messages payment_required?: true
        allow(order).to receive_messages process_payments!: true
      end

      context "when payment processing succeeds" do
        it "should finalize order when transitioning to complete state" do
          order.next

          expect {
            order.next!
          }.to change {
            order.state
          }.from("confirmation").to("complete")
            .and change {
              order.completed_at
            }
        end

        context "when credit card processing fails" do
          before { allow(order).to receive_messages process_payments!: false }

          it "should still complete the order" do
            order.next

            expect {
              order.next!
            }.to change {
              order.state
            }.from("confirmation").to("complete")
              .and change {
                order.completed_at
              }
          end
        end
      end

      context "when payment processing fails" do
        before { allow(order).to receive_messages process_payments!: false }

        it "can transition to complete" do
          order.next
          expect(order.state).to eq "confirmation"
          order.next
          expect(order.state).to eq "complete"
        end
      end
    end

    context "when current state is delivery" do
      before do
        allow(order).to receive(:ensure_available_shipping_rates)
        order.state = "delivery"
      end

      it "adjusts tax rates when transitioning to payment" do
        expect(Spree::TaxRate).to receive(:adjust).at_least(:once)
        order.next!
      end
    end
  end

  context "#can_cancel?" do
    [:pending, :backorder, :ready].each do |shipment_state|
      it "should be true if shipment_state is #{shipment_state}" do
        allow(order).to receive_messages completed?: true
        order.shipment_state = shipment_state
        expect(order.can_cancel?).to be_truthy
      end
    end

    (Spree::Shipment.state_machine.states.keys - [:pending, :backorder, :ready])
      .each do |shipment_state|
      it "should be false if shipment_state is #{shipment_state}" do
        allow(order).to receive_messages completed?: true
        order.shipment_state = shipment_state
        expect(order.can_cancel?).to be_falsy
      end
    end
  end

  context "#cancel" do
    let!(:variant) { build(:variant) }
    let!(:inventory_units) {
      [build(:inventory_unit, variant:),
       build(:inventory_unit, variant:)]
    }
    let!(:shipment) do
      shipment = build(:shipment)
      allow(shipment).to receive_messages(inventory_units:)
      allow(order).to receive_messages shipments: [shipment]
      shipment
    end

    before do
      allow(order).to receive_messages line_items: [
        build(:line_item, variant:, quantity: 2)
      ]

      allow(order).to receive_messages completed?: true
      allow(order).to receive_messages allow_cancel?: true
    end

    it "should send a cancel email" do
      # Stub methods that cause side-effects in this test
      allow(shipment).to receive(:cancel!)
      mail_message = double "Mail::Message"
      order_id = nil
      expect(Spree::OrderMailer).to receive(:cancel_email) { |*args|
        order_id = args[0]
        mail_message
      }
      expect(mail_message).to receive :deliver_later
      order.cancel!
      expect(order_id).to eq order.id
    end

    context "restocking inventory" do
      before do
        allow(shipment).to receive(:ensure_correct_adjustment)
        allow(shipment).to receive(:update_order)
        allow(Spree::OrderMailer).to receive(:cancel_email).and_return(mail_message = double)
        allow(mail_message).to receive :deliver_later
      end
    end

    context "resets payment state" do
      before do
        # Stubs methods that cause unwanted side effects in this test
        allow(Spree::OrderMailer).to receive(:cancel_email).and_return(mail_message = double)
        allow(mail_message).to receive :deliver_later
        allow(shipment).to receive(:cancel!)
      end

      context "without shipped items" do
        it "should set payment state to 'void'" do
          expect {
            order.cancel!
            order.reload
          }.to change { order.payment_state }.to("void")
        end
      end
    end
  end

  # Another regression test for Spree #729
  context "#resume" do
    context "resets payment state" do
      let!(:variant) { build(:variant) }
      before do
        allow(order).to receive_messages email: "user@spreecommerce.com"
        allow(order).to receive_messages allow_cancel?: true
        allow(order).to receive_messages allow_resume?: true
        allow(order).to receive_messages line_items:
                                           [build(:line_item, variant:, quantity: 2)]
        allow(order.line_items).to receive_messages find_by_variant_id: order.line_items.first
        order.update(total: 10)
        order.cancel!
      end

      it "should set payment state to 'balance due'" do
        expect {
          order.resume!
          order.reload
        }.to change { order.payment_state }.to("balance_due")
      end

      it "should set payment state to 'paid'" do
        expect {
          order.update(payment_total: 10)
          order.resume!
          order.reload
        }.to change { order.payment_state }.to("paid")
      end

      it "should set payment state to 'credit owed'" do
        expect {
          order.update(payment_total: 20)
          order.resume!
          order.reload
        }.to change { order.payment_state }.to("credit_owed")
      end
    end
  end
end
