module Workarea
  class OrderCancellationMetrics
    attr_reader :order, :occured_at
    delegate_missing_to :order

    def initialize(order, item_values: {}, shipping_value: nil, occured_at: Time.current)
      @order = order
      @item_values = item_values
      @shipping_value = shipping_value
      @occured_at = occured_at&.to_time
    end

    def sales_data
      @sales_data ||= begin
        based_on_items = calculate_based_on_items(items)
        refund = based_on_items[:refund] + shipping_refund_total
        {
          cancellations: 1,
          units_canceled: based_on_items[:units_canceled],
          refund: refund,
          revenue: refund
        }
      end
    end

    def user_data
      {
        email: email,
        refund: sales_data[:refund]
      }
    end

    def products
      @products ||= order.items
         .group_by(&:product_id)
         .transform_values { |items| calculate_based_on_items(items) }
         .reject { |_, v| v.blank? }
    end

    def categories
      @categories ||= items_by_via
        .select { |gid| gid.model_class.name == 'Workarea::Catalog::Category' }
        .transform_keys(&:model_id)
    end

    def searches
      @searches ||= items_by_via
        .select { |gid| gid.model_class.name == 'Workarea::Navigation::SearchResults' }
        .each_with_object({}) do |(gid, data), memo|
          query_id = gid.find.query_string.id

          memo[query_id] ||= Hash.new(0)
          memo[query_id].merge!(data) { |_, total, current| total + current }
        end
    end

    def skus
      @skus ||= order.items
        .group_by(&:sku)
        .transform_values { |items| calculate_based_on_items(items) }
        .reject { |_, v| v.blank? }
    end

    def menus
      @menus ||= categories.each_with_object({}) do |(category_id, data), memo|
        category = category_models[category_id]
        taxon_ids = [category&.taxon&.id, *category&.taxon&.parent_ids].compact

        Navigation::Menu.any_in(taxon_id: taxon_ids).pluck(:id).map(&:to_s).each do |menu_id|
          memo[menu_id] ||= Hash.new(0)
          memo[menu_id].merge!(data) { |_, total, current| total + current }
        end

        memo
      end.reject { |_, v| v.blank? }
    end

    def country
      payment.address&.country&.alpha2
    end

    def shipping_refund_total
      (@shipping_value || shipping_total || 0) * -1
    end

    def segments
      @segments ||= order.segment_ids.each_with_object({}) do |segment_id, data|
        data[segment_id] = sales_data
      end
    end

    private

    def payment
      @payment ||= Payment.find_or_initialize_by(id: order.id)
    end

    def shippings
      @shippings ||= Shipping.by_order(order.id)
    end

    def all_price_adjustments
      @all_price_adjustments ||= order.price_adjustments +
        shippings.map(&:price_adjustments).flatten
    end

    def calculate_based_on_items(items)
      Array.wrap(items).each_with_object(Hash.new(0)) do |item, memo|
        data = item_data[item.id.to_s]&.with_indifferent_access
        next unless data.present?

        memo[:units_canceled] += data[:quantity]
        memo[:refund] += data[:amount] * -1
        memo[:revenue] += data[:amount] * -1
      end
    end

    def item_data
      return @item_values if @item_values.present?

      @item_data ||= items.each_with_object({}) do |item, data|
        adjustments = PriceAdjustmentSet.new(item.price_adjustments)
        before_tax = adjustments.sum - adjustments.adjusting('tax').sum

        data[item.id.to_s] = {
          quantity: item.quantity,
          amount: before_tax + tax_total_for(item.id)
        }
      end
    end

    def items_by_via
      @items_by_via ||= order.items.each_with_object({}) do |item, memo|
        global_id = GlobalID.parse(item.via)
        next unless global_id.present?

        memo[global_id] ||= Hash.new(0)
        values = calculate_based_on_items(item)

        memo[global_id].merge!(values) { |_, sum, item| sum + item }
      end.reject { |_, v| v.blank? }
    end

    def tax_total_for(item_id)
      all_price_adjustments
        .adjusting('tax')
        .select { |a| a.data['order_item_id'].to_s == item_id.to_s }
        .sum
    end

    def category_models
      @category_models ||= Catalog::Category.any_in(id: categories.keys).to_lookup_hash
    end
  end
end