# frozen_string_literal: true

module ConvenientService
  module Service
    module Plugins
      module RescuesResultUnhandledExceptions
        module Commands
          class FormatException < Support::Command
            ##
            # @!attribute [r] exception
            #   @return [StandardError]
            #
            attr_reader :exception

            ##
            # @!attribute [r] args
            #   @return [StandardError]
            #
            attr_reader :args

            ##
            # @!attribute [r] kwargs
            #   @return [StandardError]
            #
            attr_reader :kwargs

            ##
            # @!attribute [r] block
            #   @return [StandardError]
            #
            attr_reader :block

            ##
            # @param exception [StandardError]
            # @param args [Array]
            # @param kwargs [Hash]
            # @param block [Proc, nil]
            # @return [void]
            #
            def initialize(exception:, args:, kwargs:, block:)
              @exception = exception
              @args = args
              @kwargs = kwargs
              @block = block
            end

            ##
            # @return [String]
            #
            # @note Exceptions formatting is inspired by RSpec. It has almost the same output (at least for RSpec 3).
            #
            # @example Simple exception.
            #
            #   StandardError:
            #     exception message
            #   # /gem/lib/convenient_service/factories/services.rb:120:in `result'
            #   # /gem/lib/convenient_service/core/entities/config/entities/method_middlewares/entities/caller/commands/define_method_middlewares_caller.rb:116:in `call'
            #   # /gem/lib/convenient_service/core/entities/config/entities/method_middlewares/entities/caller/commands/define_method_middlewares_caller.rb:116:in `block in result'
            #   # /gem/lib/convenient_service/dependencies/extractions/ruby_middleware/middleware/runner.rb:67:in `block (2 levels) in build_call_chain'
            #   # /gem/lib/convenient_service/core/entities/config/entities/method_middlewares/entities/chain.rb:35:in `next'
            #   # /gem/lib/convenient_service/common/plugins/caches_return_value/middleware.rb:17:in `block in next'
            #   # /gem/lib/convenient_service/support/cache.rb:110:in `fetch'
            #   # /gem/lib/convenient_service/common/plugins/caches_return_value/middleware.rb:17:in `next'
            #   # /gem/lib/convenient_service/core/entities/config/entities/method_middlewares/entities/middleware.rb:73:in `call'
            #   # /gem/lib/convenient_service/core/entities/config/entities/method_middlewares/entities/chain.rb:35:in `next'
            #
            # @example Exception with backtrace with more than 10 lines.
            #
            #   StandardError:
            #     exception message
            #   # /gem/lib/convenient_service/factories/services.rb:120:in `result'
            #   # /gem/lib/convenient_service/core/entities/config/entities/method_middlewares/entities/caller/commands/define_method_middlewares_caller.rb:116:in `call'
            #   # /gem/lib/convenient_service/core/entities/config/entities/method_middlewares/entities/caller/commands/define_method_middlewares_caller.rb:116:in `block in result'
            #   # /gem/lib/convenient_service/dependencies/extractions/ruby_middleware/middleware/runner.rb:67:in `block (2 levels) in build_call_chain'
            #   # /gem/lib/convenient_service/core/entities/config/entities/method_middlewares/entities/chain.rb:35:in `next'
            #   # /gem/lib/convenient_service/common/plugins/caches_return_value/middleware.rb:17:in `block in next'
            #   # /gem/lib/convenient_service/support/cache.rb:110:in `fetch'
            #   # /gem/lib/convenient_service/common/plugins/caches_return_value/middleware.rb:17:in `next'
            #   # /gem/lib/convenient_service/core/entities/config/entities/method_middlewares/entities/middleware.rb:73:in `call'
            #   # /gem/lib/convenient_service/core/entities/config/entities/method_middlewares/entities/chain.rb:35:in `next'
            #   # ...
            #
            # @example Exception with cause.
            #
            #   StandardError:
            #     exception message
            #   # /gem/lib/convenient_service/factories/service/class.rb:43:in `rescue in result'
            #   # /gem/lib/convenient_service/factories/service/class.rb:40:in `result'
            #   # /gem/lib/convenient_service/core/entities/config/entities/method_middlewares/entities/caller/commands/define_method_middlewares_caller.rb:116:in `call'
            #   # /gem/lib/convenient_service/core/entities/config/entities/method_middlewares/entities/caller/commands/define_method_middlewares_caller.rb:116:in `block in result'
            #   # /gem/lib/convenient_service/dependencies/extractions/ruby_middleware/middleware/runner.rb:67:in `block (2 levels) in build_call_chain'
            #   # /gem/lib/convenient_service/core/entities/config/entities/method_middlewares/entities/chain.rb:35:in `next'
            #   # /gem/lib/convenient_service/common/plugins/caches_return_value/middleware.rb:17:in `block in next'
            #   # /gem/lib/convenient_service/support/cache.rb:110:in `fetch'
            #   # /gem/lib/convenient_service/common/plugins/caches_return_value/middleware.rb:17:in `next'
            #   # /gem/lib/convenient_service/core/entities/config/entities/method_middlewares/entities/middleware.rb:73:in `call'
            #   ------------------
            #   --- Caused by: ---
            #   StandardError:
            #     cause message
            #   # /gem/lib/convenient_service/factories/service/class.rb:41:in `result'
            #
            # @internal
            #   TODO: Think about the following questions:
            #   - How to format args, kwargs and block?
            #   - Who should format them? The end-user or this plugin?
            #
            def call
              <<~MESSAGE.rstrip
                #{formatted_exception}
                #{formatted_exception_backtrace}
                #{formatted_exception_cause}
              MESSAGE
            end

            private

            ##
            # @return [String]
            #
            def formatted_exception
              <<~MESSAGE.chomp
                #{exception.class}:
                  #{exception.message}
              MESSAGE
            end

            ##
            # Formats exception backtrace. When backtrace has more then `max_backtrace_size` lines, the extra lines are trimmed.
            # That is especially important for the monitoring system with the limited amount of memory, since it may too expensive to store the full backtrace all the time.
            #
            # @return [String]
            #
            # @internal
            #   TODO: Add an ability to set `max_backtrace_size` while configuring the middleware. For example:
            #
            #     class Service
            #       include ConvenientService::Standard::Config
            #
            #       middlewares :result, scope: :class do
            #         use ConvenientService::Service::Plugins::RescuesResultUnhandledExceptions::Middleware, max_backtrace_size: 1_000
            #       end
            #
            #       # ...
            #     end
            #
            def formatted_exception_backtrace
              Commands::FormatBacktrace.call(backtrace: exception.backtrace, max_size: max_backtrace_size)
            end

            ##
            # @return [String]
            #
            def formatted_exception_cause
              Commands::FormatCause.call(cause: exception.cause)
            end

            ##
            # @return [Integer]
            #
            def max_backtrace_size
              Constants::DEFAULT_MAX_BACKTRACE_SIZE
            end
          end
        end
      end
    end
  end
end