# frozen_string_literal: true

# A service layer that handles generating Carton objects when inventory units
# are actually shipped.  It also takes care of things like updating order and
# shipment states and delivering shipment emails as needed.
class Spree::OrderShipping
  def initialize(order)
    @order = order
  end

  # A shortcut method that ships *all* inventory units in a shipment in a single
  # carton.  See also {#ship}.
  #
  # @param shipment The shipment to create a carton from.
  # @param external_number An optional external number. e.g. from a shipping company or 3PL.
  # @param tracking_number An optional tracking number.
  # @return The carton created.
  def ship_shipment(shipment, external_number: nil, tracking_number: nil, suppress_mailer: false)
    ship(
      inventory_units: shipment.inventory_units.shippable,
      stock_location: shipment.stock_location,
      address: shipment.order.ship_address,
      shipping_method: shipment.shipping_method,
      shipped_at: Time.current,
      external_number:,
      # TODO: Remove the `|| shipment.tracking` once Shipment#ship! is called by
      # OrderShipping#ship rather than vice versa
      tracking_number: tracking_number || shipment.tracking,
      suppress_mailer:
    )
  end

  # Generate a carton from the supplied inventory units and marks those units
  # as shipped.  Also sends shipment emails if appropriate and updates
  # shipment_states for associated orders.
  #
  # @param inventory_units The units to put in a carton together.
  # @param stock_location The location the carton shipped from.
  # @param address The address the carton was shipped to.
  # @param shipping_method Shipping method used for the carton.
  # @param shipped_at The time at which the shipment was shipped.
  # @param external_number An optional external number. e.g. from a shipping company or 3PL.
  # @param tracking_number An option tracking number.
  # @return The carton created.
  def ship(inventory_units:, stock_location:, address:, shipping_method:,
           shipped_at: Time.current, external_number: nil, tracking_number: nil, suppress_mailer: false)
    carton = nil

    Spree::InventoryUnit.transaction do
      inventory_units.each(&:ship!)

      carton = Spree::Carton.create!(
        stock_location:,
        address:,
        shipping_method:,
        inventory_units:,
        shipped_at:,
        external_number:,
        tracking: tracking_number
      )
    end

    inventory_units.map(&:shipment).uniq.each do |shipment|
      if shipment.inventory_units.reload.all? { |iu| iu.shipped? || iu.canceled? }
        shipment.update!(state: "shipped", shipped_at: Time.current, tracking: tracking_number)
      else
        shipment.update!(tracking: tracking_number)
      end
    end

    send_shipment_emails(carton) if stock_location.fulfillable? && !suppress_mailer # e.g. digital gift cards that aren't actually shipped
    @order.shipments.reload
    @order.recalculate

    carton
  end

  private

  def send_shipment_emails(carton)
    carton.orders.each do |order|
      Spree::Config.carton_shipped_email_class.shipped_email(order:, carton:).deliver_later
    end
  end
end
