# frozen_string_literal: false

require 'open_food_network/enterprise_fee_calculator'

module Calculator
  class DefaultTax < Spree::Calculator
    def self.description
      Spree.t(:default_tax)
    end

    def compute(computable)
      case computable
      when Spree::Order
        compute_order(computable)
      when Spree::Shipment, Spree::LineItem, Spree::Adjustment
        compute_item(computable)
      end
    end

    private

    def rate
      calculable
    end

    def compute_order(order)
      # This legacy tax calculation applies to additional taxes only, and is no longer used.
      # In theory it should never be called any more after this has been deployed.
      # If the message below doesn't show up in Bugsnag, we can safely delete this method and all
      # the related methods below it.
      Alert.raise("Calculator::DefaultTax was called with legacy tax calculations")

      calculator = OpenFoodNetwork::EnterpriseFeeCalculator.new(order.distributor,
                                                                order.order_cycle)

      [
        line_items_total(order),
        shipments_total(order),
        per_item_fees_total(order, calculator),
        per_order_fees_total(order, calculator)
      ].sum do |total|
        round_to_two_places(total * rate.amount)
      end
    end

    def line_items_total(order)
      matched_line_items = order.line_items.select do |line_item|
        line_item.variant.tax_category == rate.tax_category
      end

      matched_line_items.sum(&:total)
    end

    def shipments_total(order)
      order.shipments.select do |shipment|
        shipment.tax_category == rate.tax_category
      end.sum(&:cost)
    end

    # Finds relevant fees for each line_item,
    #   calculates the tax on them, and returns the total tax
    def per_item_fees_total(order, calculator)
      order.line_items.to_a.sum do |line_item|
        calculator.per_item_enterprise_fee_applicators_for(line_item.variant)
          .select { |applicator| applicable_rate?(applicator, line_item) }
          .sum { |applicator| applicator.enterprise_fee.compute_amount(line_item) }
      end
    end

    def applicable_rate?(applicator, line_item)
      fee = applicator.enterprise_fee
      (!fee.inherits_tax_category && fee.tax_category == rate.tax_category) ||
        (fee.inherits_tax_category && line_item.variant.tax_category == rate.tax_category)
    end

    # Finds relevant fees for whole order,
    #   calculates the tax on them, and returns the total tax
    def per_order_fees_total(order, calculator)
      calculator.per_order_enterprise_fee_applicators_for(order)
        .select { |applicator| applicator.enterprise_fee.tax_category == rate.tax_category }
        .sum { |applicator| applicator.enterprise_fee.compute_amount(order) }
    end

    def compute_item(item)
      if rate.included_in_price
        deduced_total_by_rate(item.amount, rate)
      else
        round_to_two_places(item.amount * rate.amount)
      end
    end

    def round_to_two_places(amount)
      BigDecimal(amount.to_s).round(2, BigDecimal::ROUND_HALF_UP)
    end

    def deduced_total_by_rate(total, rate)
      round_to_two_places(total - ( total / (1 + rate.amount) ) )
    end
  end
end
