# frozen_string_literal: true

require 'open_food_network/address_finder'

class CheckoutController < BaseController
  layout 'darkswarm'

  include OrderStockCheck
  include Spree::BaseHelper
  include CheckoutCallbacks
  include CheckoutSteps
  include OrderCompletion
  include CablecarResponses
  include WhiteLabel

  helper 'terms_and_conditions'
  helper 'checkout'
  helper 'spree/orders'
  helper EnterprisesHelper
  helper OrderHelper

  before_action :set_checkout_redirect
  before_action :hide_ofn_navigation, only: [:edit, :update]

  def edit
    if params[:step].blank?
      redirect_to_step_based_on_order
    else
      handle_insufficient_stock if details_step?
      update_order_state
      check_step
    end

    if payment_step? || summary_step?
      credit_payment_method = @order.distributor.payment_methods.customer_credit
      @paid_with_credit = @order.payments.find_by(payment_method: credit_payment_method)&.amount
    end

    return if available_shipping_methods.any?

    flash[:error] = I18n.t('checkout.errors.no_shipping_methods_available')
  end

  def update
    return render cable_ready: cable_car.redirect_to(url: checkout_step_path(:details)) \
      unless sufficient_stock?

    if confirm_order || update_order
      return if performed?

      check_payments_adjustments
      clear_invalid_payments
      advance_order_state
      redirect_to_step
    else
      render_error
    end
  rescue Spree::Core::GatewayError => e
    flash[:error] = I18n.t(:spree_gateway_error_flash_for_checkout, error: e.message)
    @order.update_column(:state, "payment")
    render cable_ready: cable_car.redirect_to(url: checkout_step_path(:payment))
  end

  private

  def render_error
    flash.now[:error] ||= I18n.t('checkout.errors.saving_failed')

    render status: :unprocessable_entity, cable_ready: cable_car.
      replace("#checkout", partial("checkout/checkout")).
      replace("#flashes", partial("shared/flashes", locals: { flashes: flash }))
  end

  def check_payments_adjustments
    @order.payments.each(&:ensure_correct_adjustment)
  end

  def clear_invalid_payments
    @order.payments.with_state(:invalid).delete_all
  end

  def confirm_order
    return unless summary_step? && @order.confirmation?
    return unless validate_current_step

    @order.customer.touch :terms_and_conditions_accepted_at

    # Redeem VINE voucher
    vine_voucher_redeemer = Vine::VoucherRedeemerService.new(order: @order)
    unless vine_voucher_redeemer.redeem
      # rubocop:disable Rails/DeprecatedActiveModelErrorsMethods
      flash[:error] = if vine_voucher_redeemer.errors.keys.include?(:redeeming_failed)
                        vine_voucher_redeemer.errors[:redeeming_failed]
                      else
                        I18n.t('checkout.errors.voucher_redeeming_error')
                      end
      return false
      # rubocop:enable Rails/DeprecatedActiveModelErrorsMethods
    end

    return true if redirect_to_payment_gateway

    @order.process_payments!
    @order.confirm!
    BackorderJob.check_stock(@order)
    order_completion_reset @order
  end

  def redirect_to_payment_gateway
    return unless selected_payment_method&.external_gateway?
    return unless (redirect_url = selected_payment_method.external_payment_url(order: @order))

    render cable_ready: cable_car.redirect_to(url: redirect_url)
    true
  end

  def selected_payment_method
    @selected_payment_method ||= Checkout::PaymentMethodFetcher.new(@order).call
  end

  def update_order
    return if params[:confirm_order] || @order.errors.any?

    # Checking if shipping method updated before @order get updated. We can't use this guard
    # clause in recalculate_voucher as by then the @order.shipping method would be the new one
    shipping_method_updated = @order.shipping_method&.id != params[:shipping_method_id].to_i

    @order.select_shipping_method(params[:shipping_method_id])

    @order.update(order_params)

    # We need to update voucher to take into account:
    #  * when moving away from "details" step : potential change in shipping method fees
    #  * when moving away from "payment" step : payment fees
    recalculate_voucher(shipping_method_updated) if details_step? || payment_step?
    @order.update_totals_and_states

    validate_current_step
  end

  def recalculate_voucher(shipping_method_updated)
    return if @order.voucher_adjustments.empty?

    return unless shipping_method_updated

    VoucherAdjustmentsService.new(@order).update
  end

  def validate_current_step
    Checkout::Validation.new(@order, params).call && @order.errors.empty?
  end

  def advance_order_state
    return if @order.complete?

    Orders::WorkflowService.new(@order).advance_checkout(raw_params.slice(:shipping_method_id))
  end

  def order_params
    @order_params ||= Checkout::Params.new(@order, params, spree_current_user).call
  end

  # Update order state based on the step we are loading to avoid discrepancy between step and order
  # state. We need to do this when moving back to a previous checkout step, the update action takes
  # care of moving the order state forward.
  def update_order_state
    return @order.back_to_payment if @order.confirmation? && payment_step?

    return unless @order.after_delivery_state? && details_step?

    @order.back_to_address
  end
end
