# frozen_string_literal: true

require 'active_support/concern'

# These methods were available in Spree 1, but were removed in Spree 2.  We
# would still like to use them so that we still give support to the consumers
# of these methods, making the upgrade backward compatible.
#
# Therefore we use only a single stock item per variant, which is associated to
# a single stock location per instance (default stock location) and use it to
# track the `count_on_hand` value that was previously a database column on
# variants. See
# https://github.com/openfoodfoundation/openfoodnetwork/wiki/Spree-Upgrade%3A-Stock-locations
# for details.
#
# These methods are or may become deprecated.
module VariantStock
  extend ActiveSupport::Concern

  included do
    after_update :save_stock
  end

  # Returns the number of items of the variant available.
  # Spree computes total_on_hand as the sum of the count_on_hand of all its stock_items.
  #
  # @return [Float|Integer]
  def on_hand
    total_on_hand
  end

  # Sets the stock level of the variant.
  # This will only work if there is a stock item for the variant.
  #
  # @raise [StandardError] when the variant has no stock item
  def on_hand=(new_level)
    raise_error_if_no_stock_item_available

    overwrite_stock_levels(new_level)
  end

  # Checks whether this variant is produced on demand.
  def on_demand
    # A variant that has not been saved yet or has been soft-deleted doesn't have a stock item
    #   This provides a default value for variant.on_demand
    return false if new_record? || deleted?

    stock_item&.backorderable?
  end

  # Sets whether the variant can be ordered on demand or not. Note that
  # although this modifies the stock item, it is not persisted in DB. This
  # may be done to fire a single UPDATE statement when changing various
  # variant attributes, for performance reasons.
  #
  # @raise [StandardError] when the variant has no stock item yet
  def on_demand=(new_value)
    raise_error_if_no_stock_item_available

    # There should be only one at the default stock location.
    #
    # This would be better off as `stock_items.first.save` but then, for
    # unknown reasons, it does not pass the test.
    stock_items.each do |item|
      item.backorderable = new_value
      item.save
    end
  end

  def on_demand_desired_or_current
    return on_demand_desired if new_record?

    on_demand
  end

  def on_hand_desired_or_current
    return on_hand_desired if new_record?

    on_hand
  end

  # Moving Spree::Stock::Quantifier.can_supply? to the variant enables us
  #   to override this behaviour for variant overrides
  # We can have this responsibility here in the variant because there is
  #   only one stock item per variant
  #
  # Here we depend only on variant.total_on_hand and variant.on_demand.
  #   This way, variant_overrides only need to override variant.total_on_hand and variant.on_demand.
  def can_supply?(quantity)
    on_demand || total_on_hand >= quantity
  end

  # Here we depend only on variant.total_on_hand and variant.on_demand.
  #   This way, variant_overrides only need to override variant.total_on_hand and variant.on_demand.
  def fill_status(quantity)
    on_hand = if total_on_hand.to_i >= quantity || on_demand
                quantity
              else
                [0, total_on_hand].max
              end

    backordered = 0

    [on_hand, backordered]
  end

  # We can have this responsibility here in the variant because there is
  #   only one stock item per variant
  #
  # This enables us to override this behaviour for variant overrides
  def move(quantity)
    return if deleted_at

    raise_error_if_no_stock_item_available

    stock_item.adjust_count_on_hand(quantity)
  end

  # There shouldn't be any other stock items, because we should
  # have only one stock location.
  def stock_item
    stock_items.first
  end

  private

  # Persists the single stock item associated to this variant. As defined in
  # the top-most comment, as there's a single stock location in the whole
  # database, there can only be a stock item per variant. See StockItem's
  # definition at
  # https://github.com/openfoodfoundation/spree/blob/43950c3689a77a7f493cc6d805a0edccfe75ebc2/core/app/models/spree/stock_item.rb#L3-L4
  # for details.
  def save_stock
    stock_item.save
  end

  def raise_error_if_no_stock_item_available
    message = 'You need to save the variant to create a stock item before you can set stock levels.'
    raise message if stock_items.empty?
  end

  # Overwrites stock_item.count_on_hand
  def overwrite_stock_levels(new_level)
    stock_item.adjust_count_on_hand(new_level.to_i - stock_item.count_on_hand)
  end
end
