module Evvnt
  # Internal: Handles requests to the EVVNT api and catches their responses.
  module Api
    # frozen_string_literal: true
    require 'oj'
    require "evvnt/api_error"

    extend ActiveSupport::Concern

    ##
    # Allowed HTTP verbs
    HTTP_VERBS = %i[get post put].freeze

    included do
      if Evvnt.configuration.environment == :live
        base_uri "https://api.evvnt.com"
      else
        base_uri "https://api.sandbox.evvnt.com"
      end
    end


    # Class methods to define on {Evvnt::Base}
    module ClassMethods
      private

        # Make a request of the EVVNT API.
        #
        # verb    - The HTTP verb for the request.
        # path    - The path to request from the API.
        # params  - A Hash of query params to send with the request.
        # options - A Hash of additional options for the request.
        #
        # Returns HTTParty::Response
        # Raises ArgumentError if the verb is not valid
        def api_request(verb, path, params: {},  options: {})
          unless verb.in?(HTTP_VERBS)
            raise ArgumentError, "Unrecognised HTTP verb '#{verb}'"
          end
          path = sanitize_path(path)
          log_request(verb, path, params)
          response = public_send(verb, path, query: params, headers: headers)
          log_response(response)
          case response.code
          when 200, 201
            parse_response(response, options)
          else
            raise ApiError, parse_error_response(response.body)
          end
        end

        # Log the request being sent to the API
        #
        # verb   - A Symbol of the HTTP verb for the request
        # path   - A String of the path of the request
        # params - A Hash of the request params being sent to the server
        #
        def log_request(verb, path, params = {})
          return unless Evvnt.configuration.debug
          debug <<~TEXT
            Headers: #{headers}")
            Request: #{verb.to_s.upcase} #{base_uri}#{path} #{params}
          TEXT
        end

        # Log the response from the API
        #
        # response - The Response object from the API
        #
        def log_response(response)
          return unless Evvnt.configuration.debug
          debug <<~TEXT
            Response: #{response}
            Status: #{response.code}
          TEXT
        end

        def parse_body(body)
          Oj.load(body)
        end

        ##
        # Parse a response from the API and create objects from local classes.
        #
        # response - Response object from HTTParty request.
        # options  - A Hash of options
        #
        # Returns Array
        # Returns Evvnt::Base subclass
        def parse_response(response, **options)
          json = parse_body(response.body)
          json = json[options[:object]] if options.key?(:object)
          json.is_a?(Array) ? json.map { |a| new(a) } : new(json)
        end

        def parse_error_response(body)
          body_json = parse_body(body)
          if body_json.key?("error")
            body_json["error"]
          else
            body_json["errors"].join(", ")
          end
        end

        # Ensure the path is the correct format with a leading slash and ".json" extension
        #
        # path - A String with the API request path.
        #
        # Returns String
        def sanitize_path(path)
          path = "/"  + path    unless path.starts_with?('/')
          path += ".json" unless path.ends_with?(".json")
          path
        end

        ##
        # Headers to be sent with every request.
        #
        # Returns Hash
        def headers
          {
            "Authorization" => "Basic #{auth}",
            "Content-Type"  => "application/json",
            "Accept"        => "application/json"
          }
        end

        ##
        # Key and secret as Base64 string.
        def auth
          Base64.encode64([Evvnt.configuration.api_key,
                           Evvnt.configuration.api_secret].join(":"))
        end
    end
  end
end
