# ActsAsPurchasableWizard
#

module ActsAsPurchasableWizard
  extend ActiveSupport::Concern

  module Base
    def acts_as_purchasable_wizard
      raise('please declare acts_as_wizard first') unless respond_to?(:acts_as_wizard?)
      raise('please declare acts_as_purchasable_parent first') unless respond_to?(:acts_as_purchasable_parent?)

      unless (const_get(:WIZARD_STEPS).keys & [:billing, :checkout, :submitted]).length == 3
        raise('please include a :billing, :checkout and :submitted step')
      end

      include ::ActsAsPurchasableWizard
    end
  end

  included do
    validates :owner, presence: true

    # Billing Step
    validate(if: -> { current_step == :billing && owner.present? }) do
      self.errors.add(:base, "must have a billing address") unless owner.billing_address.present?
      self.errors.add(:base, "must have an email") unless owner.email.present?
    end

    after_purchase do |_|
      raise('expected submit_order to be purchased') unless submit_order&.purchased?
      before_submit_purchased!
      submit_purchased!
      after_submit_purchased!
    end
  end

  # All Fees and Orders
  def submit_fees
    raise('to be implemented by caller')
  end

  def submit_order
    orders.first
  end

  def find_or_build_submit_fees
    submit_fees
  end

  def find_or_build_submit_order
    order = submit_order || orders.build(user: owner) # This is polymorphic user, might be an organization
    fees = submit_fees().reject { |fee| fee.marked_for_destruction? }

    # A membership could go from individual to organization
    order.user = owner

    # Adds fees, but does not overwrite any existing price.
    fees.each do |fee|
      order.add(fee) unless order.purchasables.include?(fee)
    end

    order.order_items.each do |order_item|
      fee = fees.find { |fee| fee == order_item.purchasable }
      order.remove(order_item) unless fee.present?
    end

    # From Billing Step
    order.billing_address = owner.billing_address if owner.try(:billing_address).present?

    # Important to add/remove anything
    # This will update the prices, but the purchasables must be persisted
    order.save!

    order
  end

  # Should be indempotent.
  def build_submit_fees_and_order
    return false if was_submitted?

    fees = find_or_build_submit_fees()
    raise('already has purchased submit fees') if fees.any?(&:purchased?)

    order = find_or_build_submit_order()
    raise('already has purchased submit order') if order.purchased?

    true
  end

  # Owner clicks on the Billing step. Next step is Checkout
  def billing!
    ready! && save!
  end

  # Ready to check out
  # This is called by the "ready_checkout" before_action in wizard_controller/before_actions.rb
  def ready!
    without_current_step do
      build_submit_fees_and_order
      save!
    end
  end

  # Called automatically via after_purchase hook above
  def submit_purchased!
    return false if was_submitted?

    wizard_steps[:checkout] = Time.zone.now
    submit!
  end

  # A hook to extend
  def before_submit_purchased!
  end

  def after_submit_purchased!
  end

  # Draft -> Submitted requirements
  def submit!
    raise('already submitted') if was_submitted?
    raise('expected a purchased order') unless submit_order&.purchased?

    wizard_steps[:checkout] ||= Time.zone.now
    wizard_steps[:submitted] = Time.zone.now
    submitted!
  end

  module ClassMethods
    def acts_as_purchasable_wizard?; true; end
  end

end