module Spree
  module SolidusSixSaferpay
    class ProcessAuthorizedPayment

      attr_reader :saferpay_payment, :order, :success, :user_message

      def self.call(saferpay_payment)
        new(saferpay_payment).call
      end

      def initialize(saferpay_payment)
        @saferpay_payment = saferpay_payment
        @order = saferpay_payment.order
      end

      def call
        check_liability_shift_requirements!

        validate_payment!

        # SUCCESS!

        cancel_old_solidus_payments
        saferpay_payment.create_solidus_payment!
        @success = true

        self

      rescue ::SolidusSixSaferpay::InvalidSaferpayPayment => e
        cancel_saferpay_payment
        @user_message = e.full_message
        @success = false

        self
      end

      def success?
        @success
      end

      def gateway
        raise NotImplementedError, "Must be implemented in ProcessPaymentPagePayment or ProcessTransactionPayment"
      end

      private

      def validate_payment!
        PaymentValidator.call(saferpay_payment)
      end

      # Cancels only the saferpay payment without affecting solidus
      def cancel_saferpay_payment
        if transaction_id = saferpay_payment.transaction_id
          gateway.void(transaction_id)
        end
      end

      # Cancels the solidus payments which automatically cancels the saferpay
      # payments
      def cancel_old_solidus_payments
        solidus_payments_to_cancel.each do |payment|
          # void or create refund
          payment.cancel!
        end
      end

      def check_liability_shift_requirements!
        if require_liability_shift? && !liability_shifted?
          raise ::SolidusSixSaferpay::InvalidSaferpayPayment.new(details: I18n.t(:liability_shift_not_granted, scope: [:solidus_six_saferpay, :errors]))
        end
      end

      def require_liability_shift?
        saferpay_payment.payment_method.preferred_require_liability_shift
      end

      def liability_shifted?
        saferpay_payment.liability.liability_shift
      end

      def solidus_payments_to_cancel
        order.payments.valid.where.not(state: [:void])
      end
    end
  end
end
