# frozen_string_literal: true

module Appsignal
  # @!visibility private
  class Environment
    ERROR_ON_YIELD = :APPSIGNAL_ERROR_ON_YIELD

    # Add environment metadata.
    #
    # The key and value of the environment metadata must be a String, even if
    # it's actually of another type.
    #
    # The value of the environment metadata is given as a block that captures
    # errors that might be raised while fetching the value. It will not
    # re-raise errors, but instead log them using the
    # {Appsignal.internal_logger}. This ensures AppSignal will not cause an
    # error in the application when collecting this metadata.
    #
    # @example Reporting a key and value
    #   Appsignal::Environment.report("ruby_version") { RUBY_VERSION }
    #
    # @example When a value is nil
    #   Appsignal::Environment.report("ruby_version") { nil }
    #   # Key and value do not get reported. A warning gets logged instead.
    #
    # @example When an error occurs
    #   Appsignal::Environment.report("ruby_version") { raise "uh oh" }
    #   # Error does not get reraised. A warning gets logged instead.
    #
    # @param key [String] The name of the key of the environment metadata value.
    # @yieldreturn [String] The value of the key of the environment metadata.
    # @return [void]
    def self.report(key)
      key =
        case key
        when String
          key
        else
          Appsignal.internal_logger.error "Unable to report on environment " \
            "metadata: Unsupported value type for #{key.inspect}"
          return
        end

      yielded_value =
        begin
          yield
        rescue => e
          Appsignal.internal_logger.error \
            "Unable to report on environment metadata #{key.inspect}:\n" \
              "#{e.class}: #{e}"
          ERROR_ON_YIELD
        end

      return if yielded_value == ERROR_ON_YIELD

      value =
        case yielded_value
        when TrueClass, FalseClass
          yielded_value.to_s
        when String
          yielded_value
        else
          Appsignal.internal_logger.error "Unable to report on environment " \
            "metadata #{key.inspect}: Unsupported value type for " \
            "#{yielded_value.inspect}"
          return
        end

      Appsignal::Extension.set_environment_metadata(key, value)
    rescue => e
      Appsignal.internal_logger.error "Unable to report on environment " \
        "metadata:\n#{e.class}: #{e}"
    end

    # @see report_supported_gems
    SUPPORTED_GEMS = %w[
      actioncable
      actionmailer
      activejob
      activerecord
      capistrano
      celluloid
      code_ownership
      data_mapper
      delayed_job
      dry-monitor
      elasticsearch
      excon
      faraday
      gvltools
      hanami
      hiredis
      mongo_ruby_driver
      ownership
      padrino
      passenger
      puma
      que
      rack
      rails
      rake
      redis
      redis-client
      resque
      rom
      sequel
      shoryuken
      sidekiq
      sinatra
      unicorn
      webmachine
    ].freeze

    # Report on the list of AppSignal supported gems
    #
    # This list is used to report if which AppSignal supported gems are present
    # in this app and what version. This data will help AppSignal improve its
    # support by knowing what gems and versions of gems it still needs to
    # support or can drop support for.
    #
    # It will ask Bundler to report name and version information from the gems
    # that are present in the app bundle.
    #
    # @return [void]
    def self.report_supported_gems
      return unless defined?(Bundler) # Do nothing if Bundler is not present

      bundle_gem_specs =
        if ::Bundler.rubygems.respond_to?(:installed_specs)
          ::Bundler.rubygems.installed_specs
        else
          ::Bundler.rubygems.all_specs
        end
      SUPPORTED_GEMS.each do |gem_name|
        gem_spec = bundle_gem_specs.find { |spec| spec.name == gem_name }
        next unless gem_spec

        report("ruby_#{gem_name}_version") { gem_spec.version.to_s }
      end
    rescue => e
      Appsignal.internal_logger.error "Unable to report supported gems:\n" \
        "#{e.class}: #{e}"
    end

    # Report a Ruby feature is enabled
    #
    # @param feature [String] Name of the feature to report as enabled.
    # @return [void]
    def self.report_enabled(feature)
      Appsignal::Environment.report("ruby_#{feature}_enabled") { true }
    rescue => e
      Appsignal.internal_logger.error "Unable to report integration " \
        "enabled:\n#{e.class}: #{e}"
    end
  end
end
