require 'json'
require 'logger'

module AddyLambda
  # Generic methods such as logging, error handling HTTP requests etc.
  class Common
    VARIABLE_DIRECT = 'direct'.freeze
    VARIABLE_QUERYSTRING = 'querystring'.freeze

    def self.generic_log(logger, event, context, safe_to_log_override = {})
      # Dictionary to allow certain keys to be logged
      # safe_to_log["key"] = True

      safe_to_log = {}

      # Override the dictionar (add or change allowed items)
      safe_to_log.update(safe_to_log_override)

      # logger.info('## ENVIRONMENT VARIABLES\r' +
      #             jsonpickle.encode(dict(**os.environ)))
      #
      # logger = self.logger
      logged_event = recoursive_clean(event, nil, safe_to_log)
      logger.debug('## EVENT')
      logger.debug(logged_event.to_json)
      logger.debug('## CONTEXT')
      logger.debug(context.inspect.to_json)
      # logger.info("## EVENT\r#{logged_event.to_json}")
      # logger.info("## CONTEXT\r#{context.to_json}")
    end

    def self.recoursive_clean(obj, key, safe_to_log)
      if obj.is_a?(Hash)
        # Recoursively run for dictionary objects
        # return {k: recoursive_clean(v, k, safe_to_log) for k, v in obj.items()}
        obj.map { |k, v| { k => recoursive_clean(v, k, safe_to_log) } }
      elsif obj.is_a?(Array)
        # Recoursively run for arrays
        # return [recoursive_clean(v, None, safe_to_log) for v in obj]
        obj.map { |v| [recoursive_clean(v, key, safe_to_log)] }
      elsif !!safe_to_log[key] || (key.to_s.strip.downcase.end_with?('id') && !safe_to_log.key?(key))
        # If the key is safe to log, and is specified as True, or if it's not specified but ends with "id"
        obj
      elsif !obj.nil? && (!!obj == obj || obj.is_a?(String) && %w[yes no].include?(obj.strip.downcase))
        # If it's "yes" / "no" or a boolean, it's safe to log.
        obj
      elsif obj.nil?
        # Null can be logged
        nil
      elsif obj.is_a?(String) && obj.strip.empty?
        # An empty string can be logged
        ''
      else
        # Anything else becomes a "?"
        # Note: This means that a number will become a "?" string.
        '?'
      end
    end

    def self.get_body_as_object(event)
      if event.key?('body') and event['body'].is_a? String
        JSON.parse(event['body'])
      elsif event.key?('body')
        event['body']
      else
        event
      end
    end

    def self.get_parameter(event, parameter, type, default_value = nil)
      event_body = get_body_as_object(event)

      case type
      when VARIABLE_DIRECT
        event_body.fetch(parameter, default_value)
      when VARIABLE_QUERYSTRING
        event['queryStringParameters'].fetch(parameter, default_value)
      else
        default_value
      end
    end

    # sns = Aws::SNS::Resource.new(region: region)
    def self.handle_error(sns, logger, error, context)
      region = context.invoked_function_arn.split(':')[3]
      aws_account_id = context.invoked_function_arn.split(':')[4]

      err_msg = get_error_json(context, error)
      logger.error(err_msg)

      topic = sns.topic("arn:aws:sns:#{region}:#{aws_account_id}:system-errors")
      topic.publish(message: err_msg)
    end

    def self.handle_error_and_raise(sns, logger, error, context)
      handle_error(sns, logger, error, context)
      raise error
    end

    def self.get_error_json(context, error)
      {
        "function_name": context.function_name,
        "errorType": error.class,
        "errorMessage": error.message,
        "stackTrace": error.backtrace,
        "log_group_name": context.log_group_name,
        "log_stream_name": context.log_stream_name,
        "request id": context.aws_request_id
      }.to_json
    end

    def self.raise_error_description(description)
      raise StandardError, description
    end

    def self.enable_scout_apm
      return unless ENV['SCOUT_MONITOR'] == 'true'

      require 'scout_apm'
      ScoutApm::Agent.instance.install
    end

    def self.get_request(url, params = nil, headers = nil)
      uri = URI(url)
      uri.query = URI.encode_www_form(params) if params
      request = Net::HTTP::Get.new(uri.request_uri)
      http_request(request, uri, headers)
    end

    def self.delete_request(url, headers = nil)
      uri = URI(url)
      request = Net::HTTP::Delete.new(uri.request_uri)
      http_request(request, uri, headers)
    end

    def self.post_request(url, params = nil, headers = nil)
      uri = URI(url)
      request = Net::HTTP::Post.new(uri.request_uri)
      request.set_form_data(params) if params
      http_request(request, uri, headers)
    end

    def self.http_request(request, uri, headers)
      http = Net::HTTP.new(uri.host, uri.port)
      http.use_ssl = true
      http.verify_mode = OpenSSL::SSL::VERIFY_NONE
      headers&.each { |header, value| request[header] = value }
      response = http.request(request)
      parse_response(response)
    end

    def self.parse_response(response)
      response_body = valid_json?(response.body) ? JSON.parse(response.body) : response.body
      return response_body if response.is_a?(Net::HTTPSuccess)

      raise_error_description(response_body['error_description'])
    rescue JSON::ParserError => e
      raise_error_description(e.message)
    end

    def self.logger
      @logger ||= Logger.new($stdout).tap { |logger| logger.level = ENV['LOG_LEVEL'].to_i }
    end

    def self.valid_json?(json)
      JSON.parse(json)
      true
    rescue JSON::ParserError
      false
    end
  end
end