# frozen_string_literal: true

module Jira
  module Requests
    class Base
      include ProjectServicesLoggable

      JIRA_API_VERSION = 2
      # Limit the size of the JSON error message we will attempt to parse, as the JSON is external input.
      JIRA_ERROR_JSON_SIZE_LIMIT = 5_000

      ERRORS = {
        connection: [Errno::ECONNRESET, Errno::ECONNREFUSED],
        jira_ruby:  JIRA::HTTPError,
        ssl:        OpenSSL::SSL::SSLError,
        timeout:    [Timeout::Error, Errno::ETIMEDOUT],
        uri:        [URI::InvalidURIError, SocketError]
      }.freeze
      ALL_ERRORS = ERRORS.values.flatten.freeze

      def initialize(jira_integration, params = {})
        @project = jira_integration&.project
        @jira_integration = jira_integration
      end

      def execute
        return ServiceResponse.error(message: _('Jira service not configured.')) unless jira_integration&.active?

        request
      end

      private

      attr_reader :jira_integration, :project

      # We have to add the context_path here because the Jira client is not taking it into account
      def base_api_url
        "#{context_path}/rest/api/#{api_version}"
      end

      def context_path
        client.options[:context_path].to_s
      end

      # override this method in the specific request class implementation if a differnt API version is required
      def api_version
        JIRA_API_VERSION
      end

      def client
        @client ||= jira_integration.client
      end

      def request
        response = client.get(url)
        build_service_response(response)
      rescue *ALL_ERRORS => e
        log_error('Error sending message',
          client_url: client.options[:site],
          error: {
            exception_class: e.class.name,
            exception_message: e.message,
            exception_backtrace: Gitlab::BacktraceCleaner.clean_backtrace(e.backtrace)
          }
        )

        ServiceResponse.error(message: error_message(e))
      end

      def error_message(error)
        reportable_error_message(error) ||
          s_('JiraRequest|An error occurred while requesting data from Jira. Check your Jira integration configuration and try again.')
      end

      # Returns a user-facing error message if possible, otherwise `nil`.
      def reportable_error_message(error)
        case error
        when ERRORS[:jira_ruby]
          reportable_jira_ruby_error_message(error)
        when ERRORS[:ssl]
          s_('JiraRequest|An SSL error occurred while connecting to Jira: %{message}. Try your request again.') % { message: error.message }
        when *ERRORS[:uri]
          s_('JiraRequest|The Jira API URL for connecting to Jira is not valid. Check your Jira integration API URL and try again.')
        when *ERRORS[:timeout]
          s_('JiraRequest|A timeout error occurred while connecting to Jira. Try your request again.')
        when *ERRORS[:connection]
          s_('JiraRequest|A connection error occurred while connecting to Jira. Try your request again.')
        end
      end

      # Returns a user-facing error message for a `JIRA::HTTPError` if possible,
      # otherwise `nil`.
      def reportable_jira_ruby_error_message(error)
        case error.message
        when 'Unauthorized'
          s_('JiraRequest|The credentials for accessing Jira are not valid. Check your Jira integration credentials and try again.')
        when 'Forbidden'
          s_('JiraRequest|The credentials for accessing Jira are not allowed to access the data. Check your Jira integration credentials and try again.')
        when 'Bad Request'
          s_('JiraRequest|An error occurred while requesting data from Jira. Check your Jira integration configuration and try again.')
        when /errorMessages/
          jira_ruby_json_error_message(error.message)
        end
      end

      def jira_ruby_json_error_message(error_message)
        return if error_message.length > JIRA_ERROR_JSON_SIZE_LIMIT

        begin
          messages = Gitlab::Json.parse(error_message)['errorMessages']&.to_sentence
          messages = Rails::Html::FullSanitizer.new.sanitize(messages).presence
          return unless messages

          s_('JiraRequest|An error occurred while requesting data from Jira: %{messages}. Check your Jira integration configuration and try again.') % { messages: messages }
        rescue JSON::ParserError
        end
      end

      def url
        raise NotImplementedError
      end

      def build_service_response(response)
        raise NotImplementedError
      end
    end
  end
end
