module Comee
  module Core
    class SalesOrderService
      ##
      # This method returns { success: true} if all back orders in the +params+ created successfully.
      # The +params+ contain array of back orders with the corresponding products.
      # The exact structure of +params+ looks like the following
      # [{supplier_id: '', order_number: '', order_date: '', delivery_date: '', delivery_address: '', invoice_address: '',
      #   back_order_items: [{product_id: '', requested_quantity: ''},...] }...]
      def create_back_orders(params)
        check_product_prices(params)
        check_duplicate_order_number(params)
        bulk_create_back_order(params)
      end

      ##
      # This method returns true if all products under each back order in +params+ have active price in the master price table.
      # The +params+ contain array of back orders with the corresponding products.
      # The exact structure of +params+ looks like the following
      # [{supplier_id: '', order_number: '', order_date: '', delivery_date: '', delivery_address: '', invoice_address: '',
      #   back_order_items: [{product_id: '', requested_quantity: ''},...] }...]
      def check_product_prices(params)
        queries = []
        params.each do |param|
          param[:back_order_items].map do |bo_item|
            queries << {supplier_id: param[:supplier_id], product_id: bo_item[:product_id]}
          end
        end
        master_prices = queries.inject(Comee::Core::MasterPrice.none) do |conditions, condition|
          conditions.or(Comee::Core::MasterPrice.where(condition))
        end

        raise(StandardError, compose_error_messge(queries, master_prices)) unless queries.count == master_prices.count

        true
      end

      ##
      # This method composes an error message when called from check_product_prices.
      # It recieves +queries+ and +master_prices+ as a parameter
      #   The +queries+ is an array containing list of conditions that will be joined using or connector
      #     The +queries+ param looks like [{supplier_id: '', product_id: '', active: true},...]
      #   The +master_prices+ param contains master prices fetched by the queries param given above
      # The message is composed by computing difference b/n the queries and master prices params
      def compose_error_messge(queries, master_prices)
        result = master_prices.map { |mp| {supplier_id: mp[:supplier_id], product_id: mp[:product_id]} }
        diff = queries - result
        product_names = Comee::Core::Product.where(id: diff.map { |dif| dif[:product_id] }).pluck("name").join(", ")
        supplier_names = Comee::Core::Supplier.where(id: diff.map { |dif| dif[:supplier_id] }).pluck("name").join(", ")
        "The following products #{product_names} do not have price entry for the following suppliers" \
        " #{supplier_names} in the master price table or the prices are not valid."
      end

      ##
      # This method returns false if there is no duplicate order number, otherwise it raises an error.
      # The +params+ contain array of back orders with the corresponding products.
      # The exact structure of +params+ looks like the following
      # [{supplier_id: '', order_number: '', order_date: '', delivery_date: '', delivery_address: '', invoice_address: '',
      #   back_order_items: [{product_id: '', requested_quantity: ''},...] }...]
      # The method first checks if there is duplicate order number within in the +params+ array, and if not then it checks
      # for duplicate order number from back order table.
      def check_duplicate_order_number(params)
        order_numbers = params.map { |param| param[:order_number] }
        unless order_numbers.uniq.count == order_numbers.count
          raise(StandardError, "Duplicate order number(s) #{order_numbers.select do |e|
                                                              order_numbers.count(e) > 1
                                                            end.uniq.join(', ')} exist in the payload.")
        end

        duplicate_order_numbers = Comee::Core::BackOrder.where(order_number: order_numbers)
        unless duplicate_order_numbers.count.zero?
          raise(StandardError,
                "Duplicate order number(s) #{duplicate_order_numbers.map(&:order_number).join(', ')} exist in the back order table.")
        end

        false
      end

      ##
      # This method returns items aggregated by product for a supplier.
      # It recieves +items+, +supplier_id+ and +back_order_id+ as a parameter
      #   The +items+ is an array containing list of back order items
      #     The +items+ param looks like [{product_id: '', requested_quantity: ''},...]
      #   The +supplier_id+ is the id of the supplier under which the items belong to
      #   The +back_order_id+ is the id of the backer order under which the items belong to
      def aggregate_back_order_item(items, supplier_id, back_order_id, delivery_date)
        items.group_by { |item| item[:product_id] }.map do |product_id, aggregated_items|
          total_quantity = aggregated_items.reduce(0) { |sum, agg_item| agg_item[:requested_quantity] + sum }
          {product_id: product_id, requested_quantity: total_quantity, supplier_quantity: total_quantity, supplier_id: supplier_id,
           back_order_id: back_order_id, delivery_date: delivery_date}
        end
      end

      ##
      # This method assigns current purchase price for a product under a given supplier.
      # It recieves +items+ containing array of products along with the corresponding suppliers
      # The +items+ param looks like [
      #   {product_id: '', requested_quantity: '', supplier_quantity: '', supplier_id: '', back_order_id: '' },
      #   ...
      # ]
      # It assigns price by first fetching the price of products in the items array and then iterate through the
      # items array and find the corresponding price from the fetched price
      def assign_price_to_items(items)
        queries = []
        items.map { |bo_item| queries << {supplier_id: bo_item[:supplier_id], product_id: bo_item[:product_id]} }
        master_prices = queries.inject(Comee::Core::MasterPrice.none) do |conditions, condition|
          conditions.or(Comee::Core::MasterPrice.where(condition))
        end
        items.each do |item|
          mp = master_prices.find { |price| price.supplier_id == item[:supplier_id] && price.product_id == item[:product_id] }
          item[:requested_unit_price] = mp.purchase_price
          item[:supplier_unit_price] = mp.purchase_price
          item[:from] = mp.valid_from
          item[:to] = mp.valid_from
          item.delete(:supplier_id)
        end
      end

      ##
      # This method returns { success: true} if all back orders in the +params+ created successfully.
      # The +params+ contain array of back orders with the corresponding products.
      # The exact structure of +params+ looks like the following
      # [{supplier_id: '', order_number: '', order_date: '', delivery_date: '', delivery_address: '', invoice_address: '',
      #   back_order_items: [{product_id: '', requested_quantity: ''},...] }...]
      # This method uses insert_all method to create back order and its items in bulk
      def bulk_create_back_order(params)
        back_orders = params.map { |p| {**p.except(:back_order_items), created_at: Time.now, updated_at: Time.now} }
        result = Comee::Core::BackOrder.insert_all!(back_orders, returning: %w[id supplier_id delivery_date])
        aggregated_items = []
        result.pluck("id", "supplier_id", "delivery_date").each do |r|
          supplier_detail = params.find { |p| p[:supplier_id].to_i == r[1] }
          aggregated_items << aggregate_back_order_item(supplier_detail[:back_order_items], r[1], r[0], r[2])
        end
        Comee::Core::BackOrderItem.insert_all!(assign_price_to_items(aggregated_items.flatten))
        {success: true}
      rescue StandardError => e
        {success: false, error: e.message}
      end
    end
  end
end