require "chef-utils/dist" unless defined?(ChefUtils::Dist)

class Chef
  module Formatters
    module ErrorInspectors
      # == RegistrationErrorInspector
      # Wraps exceptions that occur during the client registration process and
      # suggests possible causes.
      #--
      # TODO: Lots of duplication with the node_load_error_inspector, just
      # slightly tweaked to talk about validation keys instead of other keys.
      class RegistrationErrorInspector
        include APIErrorFormatting

        attr_reader :exception
        attr_reader :node_name
        attr_reader :config

        def initialize(node_name, exception, config)
          @node_name = node_name
          @exception = exception
          @config = config
        end

        def add_explanation(error_description)
          case exception
          when Net::HTTPClientException, Net::HTTPFatalError
            humanize_http_exception(error_description)
          when Errno::ECONNREFUSED, Timeout::Error, Errno::ETIMEDOUT, SocketError
            error_description.section("Network Error:", <<~E)
              There was a network error connecting to the #{ChefUtils::Dist::Server::PRODUCT}:
              #{exception.message}
            E
            error_description.section("Relevant Config Settings:", <<~E)
              chef_server_url  "#{server_url}"

              If your chef_server_url is correct, your network could be down.
            E
          when Chef::Exceptions::PrivateKeyMissing
            error_description.section("Private Key Not Found:", <<~E)
              Your private key could not be loaded. If the key file exists, ensure that it is
              readable by #{ChefUtils::Dist::Infra::PRODUCT}.
            E
            error_description.section("Relevant Config Settings:", <<~E)
              validation_key "#{api_key}"
            E
          when Chef::Exceptions::InvalidRedirect
            error_description.section("Invalid Redirect:", <<~E)
              Change your #{ChefUtils::Dist::Server::PRODUCT} location in client.rb to the #{ChefUtils::Dist::Server::PRODUCT}'s FQDN to avoid unwanted redirections.
            E
          when EOFError
            describe_eof_error(error_description)
          else
            "#{exception.class.name}: #{exception.message}"
          end
        end

        def humanize_http_exception(error_description)
          response = exception.response
          case response
          when Net::HTTPUnauthorized
            if clock_skew?
              error_description.section("Authentication Error:", <<~E)
                Failed to authenticate to the #{ChefUtils::Dist::Server::PRODUCT} (http 401).
                The request failed because your clock has drifted by more than 15 minutes.
                Syncing your clock to an NTP Time source should resolve the issue.
              E
            else
              error_description.section("Authentication Error:", <<~E)
                Failed to authenticate to the #{ChefUtils::Dist::Server::PRODUCT} (http 401).
              E

              error_description.section("Server Response:", format_rest_error)
              error_description.section("Relevant Config Settings:", <<~E)
                chef_server_url         "#{server_url}"
                validation_client_name  "#{username}"
                validation_key          "#{api_key}"

                If these settings are correct, your validation_key may be invalid.
              E
            end
          when Net::HTTPForbidden
            error_description.section("Authorization Error:", <<~E)
              Your validation client is not authorized to create the client for this node on the #{ChefUtils::Dist::Server::PRODUCT} (HTTP 403).
            E
            error_description.section("Possible Causes:", <<~E)
              * There may already be a client named "#{config[:node_name]}"
              * Your validation client (#{username}) may have misconfigured authorization permissions.
            E
          when Net::HTTPBadRequest
            error_description.section("Invalid Request Data:", <<~E)
              The data in your request was invalid (HTTP 400).
            E
            error_description.section("Server Response:", format_rest_error)
          when Net::HTTPNotFound
            error_description.section("Resource Not Found:", <<~E)
              The #{ChefUtils::Dist::Server::PRODUCT} returned a HTTP 404. This usually indicates that your chef_server_url configuration is incorrect.
            E
            error_description.section("Relevant Config Settings:", <<~E)
              chef_server_url "#{server_url}"
            E
          when Net::HTTPNotAcceptable
            describe_406_error(error_description, response)
          when Net::HTTPInternalServerError
            error_description.section("Unknown Server Error:", <<~E)
              The server had a fatal error attempting to load the node data.
            E
            error_description.section("Server Response:", format_rest_error)
          when Net::HTTPBadGateway, Net::HTTPServiceUnavailable
            error_description.section("Server Unavailable", "The #{ChefUtils::Dist::Server::PRODUCT} is temporarily unavailable")
            error_description.section("Server Response:", format_rest_error)
          else
            error_description.section("Unexpected API Request Failure:", format_rest_error)
          end
        end

        def username
          # config[:node_name]
          config[:validation_client_name]
        end

        def api_key
          config[:validation_key]
          # config[:client_key]
        end

        def server_url
          config[:chef_server_url]
        end

        def clock_skew?
          exception.response.body =~ /synchronize the clock/i
        end

        # Parses JSON from the error response sent by Chef Server and returns the
        # error message
        #--
        # TODO: this code belongs in Chef::REST
        def format_rest_error
          Array(Chef::JSONCompat.from_json(exception.response.body)["error"]).join("; ")
        rescue Exception
          exception.response.body
        end

      end
    end
  end
end
