module Workarea
  module Pricing
    class Discount
      # This discount allows free gifts, which are automatically
      # added to the {Workarea::Order} when it qualifies.
      #
      class FreeGift < Discount
        include Conditions::OrderTotal
        include Conditions::PromoCodes
        include Conditions::UserTags

        # @!attribute sku
        #   @return [String] the SKU of the free gift
        #
        field :sku, type: String

        # @!attribute product_ids
        #   @return [String] get the free gift when purchasing
        #     one of {Catalog::Product}
        #
        field :product_ids, type: Array, default: []
        list_field :product_ids

        # @!attribute category_ids
        #   @return [String] get the free gifts when purchasing from
        #     one of these {Catalog::Category}
        #
        field :category_ids, type: Array, default: []
        list_field :category_ids

        validates :sku, presence: true

        before_save :clean_catalog_ids

        # @private
        def self.model_name
          Discount.model_name
        end

        # Price changes apply at the item level
        #
        # @return [String]
        #
        self.price_level = 'item'
        add_qualifier :order_not_empty?
        add_qualifier :catalog_qualifies?

        # Qualifier method for whether the order is empty. You can't
        # receive a free gift on an empty order.
        #
        # @param [Workarea::Order] order
        # @return [Boolean]
        #
        def order_not_empty?(order)
          order.items.reject(&:free_gift?).any?
        end

        # Qualifier method for whether either products or categories
        # match so this order receives the discount.
        #
        # @param [Workarea::Order] order
        # @return [Boolean]
        #
        def catalog_qualifies?(order)
          return true if product_ids.blank? && category_ids.blank?
          products_qualify?(order) || categories_qualify?(order)
        end

        # Apply the discount, which adds the free gift item to the
        # order, along with a dummy price adjustment.
        #
        # @param [Workarea::Order] order
        #
        def apply(order)
          free_item = Workarea::Order::Item.new(
            {
              sku: sku,
              free_gift: true,
              quantity: 1
            }.merge(free_product_attributes(sku))
          )

          sell_price = Sku.find_or_create_by(id: sku).sell_price

          free_item.adjust_pricing(adjustment_data(sell_price, 1))
          order.add_item(free_item)
        end

        # Remove any free items this discount may have added to the order.
        #
        # @param [Workarea::Order] order
        # @param [Workarea::Shipping] shipping
        # @return [Workarea::Order]
        #
        def remove_from(order, shipping = nil)
          order.items.each do |item|
            matches = !!item.price_adjustments.detect do |adjustment|
              adjustment.data['discount_id'] == id.to_s
            end

            order.remove_item(item) if matches
          end

          order
        end

        private

        def clean_catalog_ids
          self.product_ids = product_ids.reject(&:blank?)
          self.category_ids = category_ids.reject(&:blank?)
        end

        def adjustment_data(value, quantity)
          super.merge(amount: 0.to_m)
        end

        def products_qualify?(order)
          order.items.any? { |i| i.matches_products?(product_ids) }
        end

        def categories_qualify?(order)
          order.items.any? { |i| i.matches_categories?(category_ids) }
        end

        def free_product_attributes(sku)
          ttl = Workarea.config.cache_expirations.free_gift_attributes

          Rails.cache.fetch("workarea/free_gift/#{sku}", expires_in: ttl) do
            Workarea::OrderItemDetails.find(sku).try(:to_h) || {}
          end
        end
      end
    end
  end
end