module Spree
  # Handles checkout logic.  This is somewhat contrary to standard REST convention since there is not actually a
  # Checkout object.  There's enough distinct logic specific to checkout which has nothing to do with updating an
  # order that this approach is waranted.
  class CheckoutController < BaseController
    ssl_required

    before_filter :load_order
    before_filter :ensure_valid_state

    rescue_from Spree::Core::GatewayError, :with => :rescue_from_spree_gateway_error

    respond_to :html

    # Updates the order and advances to the next state (when possible.)
    def update
      if @order.update_attributes(object_params)
        fire_event('spree.checkout.update')

        if @order.next
          state_callback(:after)
        else
          flash[:error] = t(:payment_processing_failed)
          respond_with(@order, :location => checkout_state_path(@order.state))
          return
        end

        if @order.state == "complete" || @order.completed?
          flash.notice = t(:order_processed_successfully)
          flash[:commerce_tracking] = "nothing special"
          respond_with(@order, :location => completion_route)
        else
          respond_with(@order, :location => checkout_state_path(@order.state))
        end
      else
        respond_with(@order) { |format| format.html { render :edit } }
      end
    end

    private
      def ensure_valid_state
        if params[:state] && params[:state] != 'cart' &&
           !@order.checkout_steps.include?(params[:state])
          @order.state = 'cart'
          redirect_to checkout_path
        end
      end

      # Provides a route to redirect after order completion
      def completion_route
        order_path(@order)
      end

      def object_params
        # For payment step, filter order parameters to produce the expected nested attributes for a single payment and its source, discarding attributes for payment methods other than the one selected
        if @order.payment?
          if params[:payment_source].present? && source_params = params.delete(:payment_source)[params[:order][:payments_attributes].first[:payment_method_id].underscore]
            params[:order][:payments_attributes].first[:source_attributes] = source_params
          end
          if (params[:order][:payments_attributes])
            params[:order][:payments_attributes].first[:amount] = @order.total
          end
        end
        params[:order]
      end

      def load_order
        @order = current_order
        redirect_to cart_path and return unless @order and @order.checkout_allowed?
        raise_insufficient_quantity and return if @order.insufficient_stock_lines.present?
        redirect_to cart_path and return if @order.completed?
        @order.state = params[:state] if params[:state]
        state_callback(:before)
      end

      def raise_insufficient_quantity
        flash[:error] = t(:spree_inventory_error_flash_for_insufficient_quantity)
        redirect_to cart_path
      end

      def state_callback(before_or_after = :before)
        method_name = :"#{before_or_after}_#{@order.state}"
        send(method_name) if respond_to?(method_name, true)
      end

      def before_address
        @order.bill_address ||= Address.default
        @order.ship_address ||= Address.default
      end

      def before_delivery
        return if params[:order].present?
        @order.shipping_method ||= (@order.rate_hash.first && @order.rate_hash.first[:shipping_method])
      end

      def before_payment
        current_order.payments.destroy_all if request.put?
      end

      def after_complete
        session[:order_id] = nil
      end

      def rescue_from_spree_gateway_error
        flash[:error] = t(:spree_gateway_error_flash_for_checkout)
        render :edit
      end
  end
end
