module Alula class AlulaError < StandardError attr_reader :http_status, :raw_response, :error, :message def initialize(error) if error.class == String @message = error return end @http_status = error.http_status @raw_response = error @error = error.data['error'] @message = error.data['error_description'] || error.data['message'] end def ok? false end def self.for_response(response) if !response.data['error'].nil? && !response.data['error'].empty? self.error_for_response(response) elsif !response.data['errors'].nil? && !response.data['errors'].empty? self.errors_for_response(response) elsif response.data.match(/^/) self.critical_error_for_response(response.data.scan(/
(.*)<\/pre>/))
      
      elsif response.data.match(/502 Bad Gateway/)
        self.gateway_error_for_response(response.data.scan(/502 Bad Gateway/))
      else
        message = "Unable to derive error from response: #{response.inspect}"
        Alula.logger.error message
        raise UnknownApiError.new(message)
      end

    rescue NoMethodError
      message = "Unable to derive error from response: #{response.inspect}"
      Alula.logger.error message
      raise UnknownApiError.new(message)
    end

    private

    # Handle HTML-based errors from the API
    def self.critical_error_for_response(error_text)
      InvalidRequestError.new(error_text.first.first)
    end

    # Handle Gateway errors from the API
    def self.gateway_error_for_response(error_text)
      ApiGatewayError.new(error_text.first)
    end

    # Figure out what error should be raised
    def self.error_for_response(response)
      case response.data['error']
      when 'RateLimit'
        Alula.logger.error response
        RateLimitError.new(response)
      when 'invalid_token'
        InvalidTokenError.new(response)
      when 'insufficient_scope'
        InsufficientScopeError.new(response)
      when 'server_error'
        Alula.logger.error response
        ServerError.new(response)
      else
        #
        # RPC errors are identified by jsonrpc in the body.
        return ProcedureError.new(response) if response.data['jsonrpc']

        # Error messages coming from the API are inconsistent.
        # Sometimes they are in the msg key, sometimes in the message key
        # Example: https://github.com/alula-net/alula-connect/pull/3417#issue-1985813211
        # Here we have some errors that are actually known but at least one of them is not properly formatted by the API
        message = response.data.dig('error', 'data', 'msg') ||
                  response.data.dig('error', 'data', 'message') ||
                  response.data.dig('error', 'description')
        case message
        when 'dealer does not exist'
          NotFoundError.new(message)
        when 'Insufficient User Scope'
          InsufficientScopeError.new(message)
        when 'Not Found'
          NotFoundError.new(message)
        else
          Alula.logger.error response
          raise NotImplementedError, "Unable to derive error for #{response.data['error'].to_json}"
        end
      end
    end

    # Some responses have an error array. We won't be able to raise on all errors but we can raise on the first one
    def self.errors_for_response(response)
      error = response.data['errors'].first
      case error['title']
      when 'RateLimit'
        RateLimitError.new(response)
      when 'Forbidden'
        ForbiddenError.new(error['detail'])
      when 'Not Found'
        NotFoundError.new(error['detail'])
      when 'Bad Request'
        Alula.logger.error response
        BadRequestError.new(error['detail'])
      when 'API Error Unknown'
        Alula.logger.error response
        UnknownError.new(error['detail'])
      when 'Insufficient Scope'
        InsufficientScopeError.new(error['detail'])
      else
        #
        # RPC errors are identified by jsonrpc in the body.
        return ProcedureError.new(response) if response.data['jsonrpc']

        # See comment in the error_for_response method
        message = error.dig('data', 'msg') || error.dig('data', 'message') || error['description']
        case message
        when 'dealer does not exist'
          NotFoundError.new(message)
        when 'Insufficient User Scope'
          InsufficientScopeError.new(message)
        when 'Not Found'
          NotFoundError.new(message)
        else
          Alula.logger.error response
          raise NotImplementedError, "Unable to derive error for #{response.data['errors'].to_json}"
        end
      end
    end
  end

  class UnknownApiError < AlulaError
  end

  class ApiGatewayError < AlulaError
  end

  class RateLimitError < AlulaError
  end

  class NotFoundError < AlulaError
  end

  class ForbiddenError < AlulaError
  end

  class InvalidRequestError < AlulaError
  end

  class InvalidTokenError < AlulaError
  end

  class InsufficientScopeError < AlulaError
  end

  class BadRequestError < AlulaError
  end

  class NotConfiguredError < AlulaError
  end

  class InvalidFilterFieldError < AlulaError
  end

  class InvalidSortFieldError < AlulaError
  end

  class InvalidRelationshipError < AlulaError
  end

  class InvalidRoleError < AlulaError
  end

  class ServerError < AlulaError
  end

  class UnknownError < AlulaError
  end

  class ProcedureError < AlulaError
    attr_reader :code, :full_messages

    def initialize(response)
      # Take 1 error from the request
      # TODO: Multiple errors are possible, we probably want to shlep that up
      error = response.data['error'] || response.data['errors'].first

      @http_status = response.http_status
      @raw_response = response
      @error = error['message']
      @full_messages = error.dig('data', 'message')&.split(', ') || 
                        [error['message']]
      @message = error['message']
      @code = error['code']
    end

    #
    # Provides interface mirroring to success responses 
    def ok?
      false
    end
  end

  class ValidationError < AlulaError
    attr_reader :errors

    def initialize(errors)
      @errors = errors
    end
  end
end