# frozen_string_literal: true

module Spree
  class OrdersController < ::BaseController
    include OrderCyclesHelper
    include Rails.application.routes.url_helpers
    include CablecarResponses
    include WhiteLabel

    layout 'darkswarm'

    rescue_from ActiveRecord::RecordNotFound, with: :render404
    helper 'spree/orders'

    respond_to :html, :json

    before_action :check_authorization
    before_action :set_order_from_params, only: :show
    before_action :set_current_order, only: [:edit, :update]
    before_action :filter_order_params, only: :update

    prepend_before_action :require_order_authentication, only: :show
    prepend_before_action :require_order_cycle, only: :edit
    prepend_before_action :require_distributor_chosen, only: :edit
    before_action :check_hub_ready_for_checkout, only: :edit
    before_action :check_at_least_one_line_item, only: :update

    before_action only: [:show, :edit] do
      hide_ofn_navigation(@order.distributor)
    end

    def show
      credit_payment_method = @order.distributor.payment_methods.customer_credit
      credit_payment = @order.payments.find_by(payment_method: credit_payment_method)
      @paid_with_credit = credit_payment&.amount

      @payment_total = @order.payment_total - @paid_with_credit.to_f
    end

    def empty
      if @order = current_order
        @order.empty!
      end

      redirect_to main_app.cart_path
    end

    # Patching to redirect to shop if order is empty
    def edit
      @insufficient_stock_lines = @order.insufficient_stock_lines
      @unavailable_order_variants = OrderCycles::DistributedVariantsService.
        new(current_order_cycle, current_distributor).unavailable_order_variants(@order)

      if @order.line_items.empty?
        redirect_to main_app.shop_path
      else
        associate_user

        if @order.insufficient_stock_lines.present? || @unavailable_order_variants.present?
          flash.now[:error] = t("spree.orders.error_flash_for_unavailable_items")
        end
      end
    end

    def update
      @insufficient_stock_lines = []
      @order = order_to_update
      unless @order
        flash[:error] = t(:order_not_updated)
        redirect_to(main_app.root_path) && return
      end

      # This action is called either from the cart page when the order is not yet complete, or from
      # the edit order page (frontoffice) if the hub allows users to update completed orders.
      if @order.contents.update_cart(order_params)
        @order.recreate_all_fees! # Enterprise fees on line items and on the order itself

        # Re apply the voucher
        OrderManagement::Order::Updater.new(@order).update_voucher

        if @order.complete?
          @order.update_payment_fees!
          @order.create_tax_charge!
        end

        AmendBackorderJob.perform_later(@order) if @order.completed?

        respond_with(@order) do |format|
          format.html do
            if params.key?(:checkout)
              @order.next_transition.run_callbacks if @order.cart?
              redirect_to main_app.checkout_step_path("address")
            elsif @order.complete?
              redirect_to main_app.order_path(@order)
            else
              redirect_to main_app.cart_path
            end
          end
        end
      else
        # Show order with original values, not newly entered ones
        @insufficient_stock_lines = @order.insufficient_stock_lines
        @order.line_items.reload
        respond_with(@order)
      end
    end

    def cancel
      @order = Spree::Order.find_by!(number: params[:id])
      authorize! :cancel, @order

      if Orders::CustomerCancellationService.new(@order).call
        flash[:success] = I18n.t(:orders_your_order_has_been_cancelled)
      else
        flash[:error] = I18n.t(:orders_could_not_cancel)
      end
      render status: :found,
             cable_ready: cable_car.redirect_to(url: request.referer || main_app.order_path(@order))
    end

    private

    def set_order_from_params
      @order = Spree::Order.find_by!(number: params[:id])
    end

    def set_current_order
      @order = current_order(true)
    end

    def check_authorization
      session[:access_token] ||= params[:order_token]
      order = Spree::Order.find_by(number: params[:id]) || current_order

      if order
        authorize! :edit, order, session[:access_token]
      else
        authorize! :create, Spree::Order
      end
    end

    def filter_order_params
      return unless params[:order] && params[:order][:line_items_attributes]

      params[:order][:line_items_attributes] =
        remove_missing_line_items(params[:order][:line_items_attributes])
    end

    def remove_missing_line_items(attrs)
      attrs.select do |_i, line_item|
        Spree::LineItem.find_by(id: line_item[:id])
      end
    end

    def discard_empty_line_items
      @order.line_items = @order.line_items.select { |li| li.quantity > 0 }
    end

    def require_order_authentication
      return if session[:access_token] || params[:order_token] || spree_current_user

      store_location_for :spree_user, request.original_fullpath

      flash[:error] = I18n.t("spree.orders.edit.login_to_view_order")
      redirect_to main_app.root_path(anchor: "/login", after_login: request.original_fullpath)
    end

    def order_to_update
      return @order_to_update if defined? @order_to_update
      return @order_to_update = current_order unless params[:id]

      @order_to_update = changeable_order_from_number
    end

    # If a specific order is requested, return it if it is COMPLETE and
    # changes are allowed and the user has access. Return nil if not.
    def changeable_order_from_number
      order = Spree::Order.complete.find_by(number: params[:id])
      return nil unless order&.changes_allowed? && can?(:update, order)

      order
    end

    def check_at_least_one_line_item
      return unless order_to_update&.complete?

      items = params[:order][:line_items_attributes]
        &.select{ |_k, attrs| attrs["quantity"].to_i > 0 }

      return unless items.empty?

      flash[:error] = I18n.t(:orders_cannot_remove_the_final_item)
      redirect_to main_app.order_path(order_to_update)
    end

    def order_params
      params.require(:order).permit(
        :distributor_id, :order_cycle_id,
        line_items_attributes: [:id, :quantity]
      )
    end
  end
end
