require 'json'
require 'puppet/error'
require 'puppet/forge'

# Puppet::Forge specific exceptions
module Puppet::Forge::Errors

  # This exception is the parent for all Forge API errors
  class ForgeError < Puppet::Error
    # This is normally set by the child class, but if it is not this will
    # fall back to displaying the message as a multiline.
    #
    # @return [String] the multiline version of the error message
    def multiline
      self.message
    end
  end

  # This exception is raised when there is an SSL verification error when
  # communicating with the forge.
  class SSLVerifyError < ForgeError
    # @option options [String] :uri The URI that failed
    # @option options [String] :original the original exception
    def initialize(options)
      @uri     = options[:uri]
      original = options[:original]

      super(_("Unable to verify the SSL certificate at %{uri}") % { uri: @uri }, original)
    end

    # Return a multiline version of the error message
    #
    # @return [String] the multiline version of the error message
    def multiline
      message = []
      message << _('Could not connect via HTTPS to %{uri}') % { uri: @uri }
      message << _('  Unable to verify the SSL certificate')
      message << _('    The certificate may not be signed by a valid CA')
      message << _('    The CA bundle included with OpenSSL may not be valid or up to date')
      message.join("\n")
    end
  end

  # This exception is raised when there is a communication error when connecting
  # to the forge
  class CommunicationError < ForgeError
    # @option options [String] :uri The URI that failed
    # @option options [String] :original the original exception
    def initialize(options)
      @uri     = options[:uri]
      original = options[:original]
      @detail  = original.message

      message = _("Unable to connect to the server at %{uri}. Detail: %{detail}.") % { uri: @uri, detail: @detail }
      super(message, original)
    end

    # Return a multiline version of the error message
    #
    # @return [String] the multiline version of the error message
    def multiline
      message = []
      message << _('Could not connect to %{uri}') % { uri: @uri }
      message << _('  There was a network communications problem')
      message << _("    The error we caught said '%{detail}'") % { detail: @detail }
      message << _('    Check your network connection and try again')
      message.join("\n")
    end
  end

  # This exception is raised when there is a bad HTTP response from the forge
  # and optionally a message in the response.
  class ResponseError < ForgeError
    # @option options [String] :uri The URI that failed
    # @option options [String] :input The user's input (e.g. module name)
    # @option options [String] :message Error from the API response (optional)
    # @option options [Net::HTTPResponse] :response The original HTTP response
    def initialize(options)
      @uri     = options[:uri]
      @message = options[:message]
      response = options[:response]
      @response = "#{response.code} #{response.message.strip}"

      begin
        body = JSON.parse(response.body)
        if body['message']
          @message ||= body['message'].strip
        end
      rescue JSON::ParserError
      end

      message = if @message
                  _("Request to Puppet Forge failed.") + ' ' + _("Detail: %{detail}.") % { detail: "#{@message} / #{@response}" }
                else
                  _("Request to Puppet Forge failed.") + ' ' + _("Detail: %{detail}.") % { detail: @response }
                end
      super(message, original)
    end

    # Return a multiline version of the error message
    #
    # @return [String] the multiline version of the error message
    def multiline

      message = []
      message << _('Request to Puppet Forge failed.')
      message << _('  The server being queried was %{uri}') % { uri: @uri }
      message << _("  The HTTP response we received was '%{response}'") % { response: @response }
      message << _("  The message we received said '%{message}'") % { message: @message } if @message
      message.join("\n")
    end
  end

end
