# frozen_string_literal: true

module ConvenientService
  module Service
    module Plugins
      module CanHaveSteps
        module Entities
          class Step
            module Plugins
              module CanHaveFallbacks
                class Middleware < MethodChainMiddleware
                  intended_for :result, entity: :step

                  ##
                  # @return [ConvenientService::Service::Plugins::CanHaveSteps::Entities::Step]
                  #
                  alias_method :step, :entity

                  ##
                  # @return [ConvenientService::Service::Plugins::HasJSendResult::Entities::Result]
                  #
                  def next(...)
                    result = chain.next(...)

                    case result.status.to_sym
                    when :success
                      result
                    when :failure
                      fallback_failure_step? ? fallback_failure_result(...) : result
                    when :error
                      fallback_error_step? ? fallback_error_result(...) : result
                    end
                  end

                  private

                  ##
                  # @return [Boolean]
                  #
                  def fallback_failure_step?
                    step.fallback_failure_step? || fallback_true_step_as_failure_step?
                  end

                  ##
                  # @return [Boolean]
                  #
                  def fallback_true_step_as_failure_step?
                    step.fallback_true_step? && fallback_true_status == :failure
                  end

                  ##
                  # @return [Boolean]
                  #
                  def fallback_error_step?
                    step.fallback_error_step? || fallback_true_step_as_error_step?
                  end

                  ##
                  # @return [Boolean]
                  #
                  def fallback_true_step_as_error_step?
                    step.fallback_true_step? && fallback_true_status == :error
                  end

                  ##
                  # @return [Symbol]
                  #
                  def fallback_true_status
                    middleware_arguments.kwargs.fetch(:fallback_true_status) { :failure }
                  end

                  ##
                  # @return [ConvenientService::Service::Plugins::HasJSendResult::Entities::Result]
                  # @raise [ConvenientService::Service::Plugins::CanHaveSteps::Entities::Step::Plugins::CanHaveFallbacks::Exceptions::FallbackResultIsNotOverridden, ConvenientService::Service::Plugins::CanHaveSteps::Entities::Step::Plugins::CanHaveFallbacks::Exceptions::MethodStepCanNotHaveFallback]
                  #
                  def fallback_failure_result
                    refute_method_step!

                    fallback_failure_result_own_method&.call || fallback_result_own_method&.call || ::ConvenientService.raise(Exceptions::FallbackResultIsNotOverridden.new(step: step, service: service, status: :failure))
                  end

                  ##
                  # @return [ConvenientService::Service::Plugins::HasJSendResult::Entities::Result]
                  # @raise [ConvenientService::Service::Plugins::CanHaveSteps::Entities::Step::Plugins::CanHaveFallbacks::Exceptions::FallbackResultIsNotOverridden, ConvenientService::Service::Plugins::CanHaveSteps::Entities::Step::Plugins::CanHaveFallbacks::Exceptions::MethodStepCanNotHaveFallback]
                  #
                  def fallback_error_result
                    refute_method_step!

                    fallback_error_result_own_method&.call || fallback_result_own_method&.call || ::ConvenientService.raise(Exceptions::FallbackResultIsNotOverridden.new(step: step, service: service, status: :error))
                  end

                  ##
                  # @return [Method, nil]
                  #
                  def fallback_failure_result_own_method
                    Utils::Object.own_method(service, :fallback_failure_result, private: true)
                  end

                  ##
                  # @return [Method, nil]
                  #
                  def fallback_error_result_own_method
                    Utils::Object.own_method(service, :fallback_error_result, private: true)
                  end

                  ##
                  # @return [Method, nil]
                  #
                  def fallback_result_own_method
                    Utils::Object.own_method(service, :fallback_result, private: true)
                  end

                  ##
                  # @return [ConvenientService::Service]
                  #
                  # @internal
                  #   IMPORTANT: `step.service.klass.new(**input_values)` is the reason, why services should have only kwargs as arguments.
                  #
                  def service
                    @service ||= step.service.klass.new(**step.input_values)
                  end

                  ##
                  # @return [void]
                  # @raise [ConvenientService::Service::Plugins::CanHaveSteps::Entities::Step::Plugins::CanHaveFallbacks::Exceptions::MethodStepCanNotHaveFallback]
                  #
                  # @internal
                  #   TODO: Consider to move this assertion to the build time.
                  #
                  def refute_method_step!
                    return unless step.method_step?

                    ::ConvenientService.raise Exceptions::MethodStepCanNotHaveFallback.new(step: step)
                  end
                end
              end
            end
          end
        end
      end
    end
  end
end