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

require 'net/http'
require 'contrast/utils/net_http_base'
require 'contrast/components/logger'
require 'contrast/utils/object_share'
require 'contrast/agent/version'
require 'json'

module Contrast
  module Utils
    # This module creates a Net::HTTP client and initiates a connection to the provided result
    class TelemetryClient < NetHttpBase
      include Contrast::Components::Logger::InstanceMethods
      ENDPOINT = 'api/v1/telemetry/metrics' # /TelemetryEvent.path
      EXCEPTIONS = 'api/v1/telemetry/exceptions' # /TelemetryExceptions::Event.path
      SERVICE_NAME = 'Telemetry'
      # This method initializes the Net::HTTP client we'll need. it will validate
      # the connection and make the first request. If connection is valid and response
      # is available then the open connection is returned.
      #
      # @param url [String]
      # @return [Net::HTTP, nil] Return open connection or nil
      def initialize_connection url
        super(SERVICE_NAME, url, use_proxy: false, use_custom_cert: false)
      end

      # This method will be responsible for building the request. Because the telemetry collector expects to receive
      # multiple events in a single request, we must always wrap the event in an array, even if there is only one.
      #
      # @param event [Contrast::Agent::Telemetry::Event, Array<Contrast::Agent::Telemetry::TelemetryException::Event>]
      # @return [Net::HTTP::Post]
      def build_request event
        return unless valid_event?(event)

        string_body = if event.cs__is_a?(Contrast::Agent::Telemetry::TelemetryException::Event)
                        [event.to_controlled_hash]
                      else
                        [event.to_hash]
                      end

        header = {
            'User-Agent' => "<#{ Contrast::Utils::ObjectShare::RUBY }>-<#{ Contrast::Agent::VERSION }>",
            'Content-Type' => 'application/json'
        }
        request = Net::HTTP::Post.new(build_path(event), header)
        request.body = string_body.to_json
        request
      end

      # This method will create the actual request and send it
      # @param event[Contrast::Agent::Telemetry::Event]
      # @param connection[Net::HTTP]
      def send_request event, connection
        return if connection.nil? || event.nil?
        return unless valid_event?(event)

        req = build_request(event)
        connection.request(req)
      end

      # This method will handle the response from the tenant
      # @param res [Net::HTTPResponse]
      # @return sleep_time [Integer, nil]
      def handle_response res
        status_code = res.code.to_i
        ready_after = if res.to_hash.keys.map(&:downcase).include?('ready-after')
                        res['Ready-After']
                      else
                        60
                      end
        ready_after if status_code == 429
      end

      # This method will be responsible for validating the event. Valid if event is of a known
      # Contrast::Agent::Telemetry type
      #
      # @param event [Object]
      def valid_event? event
        return true if event.cs__is_a?(Contrast::Agent::Telemetry::Event)
        return true if event.cs__is_a?(Contrast::Agent::Telemetry::StartupMetricsEvent)
        return true if event.cs__is_a?(Contrast::Agent::Telemetry::TelemetryException::Event)

        false
      end

      private

      # The telemetry instance accepts any path to #{ Contrast::Agent::Telemetry::URL }#{ ENDPOINT }, using the
      # remainder of the path to segregate messages.
      #
      # @param event [Contrast::Agent::Telemetry::Event, Contrast::Agent::Telemetry::TelemetryException::Event]
      # @return [String] the fully qualified path to send the request
      def build_path event
        endpoint = Array(event).all?(Contrast::Agent::Telemetry::TelemetryException::Event) ? EXCEPTIONS : ENDPOINT
        path = endpoint == EXCEPTIONS ? Contrast::Agent::Telemetry::TelemetryException::Event.path : event.path
        "#{ Contrast::Agent::Telemetry::Base::URL }#{ endpoint }#{ path }"
      end
    end
  end
end