module Returnly
  class Refunder
    include Returnly::RefundsConfiguration

    attr_accessor :order,
                  :line_items,
                  :product_refund_amount,
                  :shipping_refund_amount,
                  :customer_return,
                  :return_item_amount_calculator,
                  :return_item_restock_policy,
                  :refund_calculator

    def initialize(order:, line_items:, product_refund_amount:, shipping_refund_amount:)
      self.order = order
      self.line_items = line_items
      self.product_refund_amount = product_refund_amount
      self.shipping_refund_amount = shipping_refund_amount

      configure
    end

    def process_return_items
      each_return_item do |return_item|
        return_item.amount               = return_item_amount_calculator.return_item_refund_amount return_item
        return_item.additional_tax_total = 0
        return_item.resellable           = return_item_restock_policy.should_return_item? return_item
      end
    end

    def proceed!
      return proceed_without_line_items if line_items.empty?
      return proceed_with_product_zero_amount if product_refund_amount.to_d.zero?
      process_return_items

      if customer_return.save!
        perform_reimbursement
        RefundPresenter.present_refund(self)
      end
    end

    def refund_available_amount
      @available_amount ||= Money.from_amount(product_available_amount)
    end

    def reimbursement
      @_reimbursement ||= Spree::Reimbursement.build_from_customer_return(customer_return)
    end

    def product_available_amount
      total = order.total - (order.shipment_total + refund_calculator.shipping_tax + refunds)
      [total, product_refund_amount.to_d].min
    end

    private

    def add_shipping_amount!
      ::ReimbursementShipping.new(reimbursement).update!(shipping_refund_amount.to_d)
    end

    def refunds
      order.refunds.sum(&:amount).to_d.round(2, :down)
    end

    def configure
      self.return_item_amount_calculator = return_item_amount_calculator_class.new(self)
      self.return_item_restock_policy = return_item_restock_policy_class.new(self)
      self.refund_calculator = refund_calculator_class.new(order, line_items)
    end

    def proceed_without_line_items
      self.customer_return = Returnly::Builders::CustomerReturn.build_by_stock_location(stock_location)
      reimburse_without_items!
      RefundPresenter.present_refund(self)
    end

    def proceed_with_product_zero_amount
      Returnly::Services::MarkItemsAsReturned.new(order, line_items).perform!
      RefundPresenter.present_refund_with_zero_amount(self)
    end

    def perform_reimbursement
      Returnly::Services::CreateReimbursement.new(reimbursement).perform!
      add_shipping_amount!
    end

    def reimburse_without_items!
      reimbursement_total = product_refund_amount.to_d + shipping_refund_amount.to_d
      @_reimbursement = Spree::Reimbursement.new(
        order: order,
        total: reimbursement_total.round(2, :down)
      )
      reimbursement.save!
      ::ReimbursementType::OriginalPaymentNoItems.reimburse(reimbursement, nil, false)
    end

    def each_return_item
      return_items = refund_calculator.line_items_return_items.values.flatten
      self.customer_return = Returnly::Builders::CustomerReturn.build_by_return_items(return_items)

      return_items.each do |return_item|
        yield return_item
      end
    end

    def stock_location
      order.shipments.last.stock_location
    end
  end
end
