# frozen_string_literal: true

module Onfido
  class Resource # rubocop:todo Metrics/ClassLength
    VALID_HTTP_METHODS = %i[get post put delete].freeze
    REQUEST_TIMEOUT_HTTP_CODE = 408

    def initialize(options)
      @rest_client = options.rest_client
    end

    private

    attr_reader :rest_client

    def get(path:)
      handle_request { rest_client[path].get }
    end

    def post(path:, payload: nil)
      handle_request { rest_client[path].post(payload) }
    end

    def put(path:, payload: nil)
      handle_request { rest_client[path].put(payload) }
    end

    def delete(path:)
      handle_request { rest_client[path].delete }
    end

    def handle_request
      response = yield

      # response should be parsed only when there is a response expected
      parse(response) unless response.code == 204 # no_content
    rescue RestClient::ExceptionWithResponse => e
      if e.response && !timeout_response?(e.response)
        handle_api_error(e.response)
      else
        handle_restclient_error(e)
      end
    rescue RestClient::Exception, Errno::ECONNREFUSED => e
      handle_restclient_error(e)
    end

    def parse(response)
      content_type = response.headers[:content_type]
      if content_type&.include?('application/json')
        JSON.parse(response.body.to_s)
      else
        response.body
      end
    rescue JSON::ParserError
      general_api_error(response.code, response.body)
    end

    def timeout_response?(response)
      response.code.to_i == REQUEST_TIMEOUT_HTTP_CODE
    end

    # There seems to be a serialization issue with the HTTP client
    # which does not serialize the payload properly.
    # Have a look here https://gist.github.com/PericlesTheo/cb35139c57107ab3c84a

    def build_query(payload)
      if payload[:file]
        payload
      else
        Rack::Utils.build_nested_query(payload)
      end
    end

    def handle_api_error(response)
      parsed_response = parse(response)

      general_api_error(response.code, response.body) unless parsed_response['error']

      error_class = response.code.to_i >= 500 ? ServerError : RequestError

      raise error_class.new(
        parsed_response['error']['message'],
        response_code: response.code,
        response_body: response.body
      )
    end

    def general_api_error(response_code, response_body)
      error_class = response_code.to_i >= 500 ? ServerError : RequestError

      raise error_class.new(
        "Invalid response object from API: #{response_body} " \
        "(HTTP response code was #{response_code})",
        response_code: response_code,
        response_body: response_body
      )
    end

    def handle_restclient_error(error) # rubocop:todo Metrics/MethodLength
      connection_message =
        'Please check your internet connection and try again. ' \
        'If this problem persists, you should let us know at info@onfido.com.'

      message =
        case error
        when RestClient::RequestTimeout
          "Could not connect to Onfido . #{connection_message}"

        when RestClient::ServerBrokeConnection
          "The connection to the server broke before the request completed. #{connection_message}"

        when RestClient::SSLCertificateNotVerified
          "Could not verify Onfido's SSL certificate. Please make sure " \
          'that your network is not intercepting certificates. '

        when RestClient::BadGateway
          "Could not connect to Onfido. Server may be overloaded." \

        when SocketError
          'Unexpected error when trying to connect to Onfido. ' \
          'You may be seeing this message because your DNS is not working. ' \
          "To check, try running 'host onfido.com' from the command line."

        else
          'Unexpected error communicating with Onfido. ' \
          'If this problem persists, let us know at info@onfido.com.'
        end

      full_message = message + "\n\n(Network error: #{error.message})"

      raise ConnectionError, full_message
    end

    def validate_file!(file)
      return if file.respond_to?(:read) && file.respond_to?(:path)

      raise ArgumentError, 'File must be a `File`-like object which responds to ' \
                           '`#read` and `#path`'
    end
  end
end