# frozen_string_literal: true

require 'active_model'

module SolidusBraintree
  class TransactionImport
    class InvalidImportError < StandardError; end

    include ActiveModel::Model

    validate do
      errors.add("Address", "is invalid") if address&.invalid?

      if transaction.invalid?
        transaction.errors.each do |error|
          errors.add(error.attribute, error.message)
        end
      end
      errors.none?
    end

    attr_reader :transaction, :order

    def initialize(order, transaction)
      @order = order
      @transaction = transaction
    end

    def source
      SolidusBraintree::Source.new(
        nonce: transaction.nonce,
        payment_type: transaction.payment_type,
        payment_method: transaction.payment_method,
        paypal_funding_source: transaction.paypal_funding_source,
        device_data: transaction.device_data,
        user: user
      )
    end

    delegate :user, to: :order

    def import!(end_state, restart_checkout: false)
      if valid?
        order.email = user&.email || transaction.email

        if address
          order.shipping_address = order.billing_address = address
          # work around a bug in most solidus versions
          # about tax zone cachine between address changes
          order.instance_variable_set(:@tax_zone, nil)
        end

        payment = order.payments.new(
          source: source,
          payment_method: transaction.payment_method,
          amount: order.total
        )

        order.save!
        order.restart_checkout_flow if restart_checkout
        advance_order(payment, end_state)
      else
        raise InvalidImportError,
          "Validation failed: #{errors.full_messages.join(', ')}"
      end
    end

    def address
      transaction.address && transaction.address.to_spree_address.tap do |address|
        address.phone = transaction.phone
      end
    end

    def state_before_current?(state)
      steps = order.checkout_steps
      steps.index(state) < (steps.index(order.state) || 0)
    end

    protected

    def advance_order(payment, end_state)
      return if state_before_current?(end_state)

      until order.state == end_state
        order.next!
        update_payment_total(payment) if order.payment?
      end
    end

    def update_payment_total(payment)
      payment_total = order.payments.where(state: %w[checkout pending]).sum(:amount)
      payment_difference = order.outstanding_balance - payment_total

      return unless payment_difference != 0

      payment.update!(amount: payment.amount + payment_difference)
    end
  end
end