# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
# frozen_string_literal: true

module Contrast
  module Utils
    # Method utility used by Contrast::Logger::log
    module LogUtils
      DEFAULT_NAME     = 'contrast.log'
      DEFAULT_LEVEL    = ::Ougai::Logging::Severity::INFO
      VALID_LEVELS     = ::Ougai::Logging::Severity::SEV_LABEL
      STDOUT_STR       = 'STDOUT'
      STDERR_STR       = 'STDERR'
      PROGNAME         = 'Contrast Agent'
      DATE_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%L%z'

      private

      def build path: STDOUT_STR, level_const: DEFAULT_LEVEL
        logger = case path
                 when STDOUT_STR, STDERR_STR
                   ::Ougai::Logger.new(Object.cs__const_get(path))
                 else
                   ::Ougai::Logger.new(path)
                 end
        add_contrast_loggers(logger)
        logger.progname = PROGNAME
        logger.level = level_const
        logger.formatter = Contrast::Logger::Format.new
        logger.formatter.datetime_format = DATE_TIME_FORMAT
        logger
      end

      def add_contrast_loggers logger
        logger.extend(Contrast::Logger::Application)
        logger.extend(Contrast::Logger::Request)
        logger.extend(Contrast::Logger::Time)
      end

      # Determine the valid path to which to log, given the precedence of config > settings > default.
      #
      # @param log_file [String, nil] the file to which to log as provided by the settings retrieved from the
      #   TeamServer.
      # @return [String] the path to which to log or STDOUT / STDERR if one of those values provided.
      def find_valid_path log_file
        config = ::Contrast::CONFIG.root.agent.logger
        config_path = config&.path&.length.to_i.positive? ? config.path : nil
        valid_path(config_path || log_file)
      end

      def valid_path path
        path = path.nil? ? Contrast::Utils::ObjectShare::EMPTY_STRING : path
        return path if path == STDOUT_STR
        return path if path == STDERR_STR

        path = DEFAULT_NAME if path.empty?
        if write_permission?(path)
          path
        elsif write_permission?(DEFAULT_NAME)
          # Log once when the path is invalid. We'll change to this path, so no
          # need to log again.
          if previous_path != DEFAULT_NAME
            $stdout.puts "[!] Unable to write to '#{ path }'. Writing to default log '#{ DEFAULT_NAME }' instead."
          end
          DEFAULT_NAME
        else
          # Log once when the path is invalid. We'll change to this path, so no
          # need to log again.
          $stdout.puts "[!] Unable to write to '#{ path }'. Writing to standard out instead."
          STDOUT_STR
        end
      end

      # Determine the valid level to which to log, given the precedence of config > settings > default.
      #
      # @param log_level [String, nil] the level at which to log as provided by the settings retrieved from the
      #   TeamServer.
      # @return [::Ougai::Logging::Severity] the level at which to log
      def find_valid_level log_level
        config = ::Contrast::CONFIG.root.agent.logger
        config_level = config&.level&.length&.positive? ? config.level : nil

        valid_level(config_level || log_level)
      end

      def valid_level level
        level ||= DEFAULT_LEVEL
        level = level.upcase
        if VALID_LEVELS.include?(level)
          Object.cs__const_get("::Ougai::Logging::Severity::#{ level }")
        else
          DEFAULT_LEVEL
        end
      rescue StandardError
        DEFAULT_LEVEL
      end

      # Log that the Agent log has changed and include some default information at the start of the log.
      def log_update
        logger.debug('Initialized new contrast agent logger')
        logger.debug_with_time('middleware: log environment') do
          logger.application_environment
          logger.application_configuration
          logger.application_libraries
        end
      end
    end
  end
end