require "chef/server_api"
require "chef/http/simple_json"
require "chef/http/simple"
require_relative "api_client/errors"
require "chef/config"
require "chef/cookbook_manifest"

module Berkshelf
  module RidleyCompatAPI
    def initialize(**opts)
      opts = opts.dup
      opts[:ssl] ||= {}
      chef_opts = {}
      chef_opts[:rest_timeout]         = opts[:timeout] if opts[:timeout] # opts[:open_timeout] is ignored on purpose
      chef_opts[:headers]              = opts[:headers] if opts[:headers]
      chef_opts[:client_name]          = opts[:client_name] if opts[:client_name]
      chef_opts[:signing_key_filename] = opts[:client_key] if opts[:client_key]
      chef_opts[:verify_api_cert]      = opts[:ssl][:verify] || opts[:ssl][:verify].nil?
      chef_opts[:ssl_verify_mode]      = chef_opts[:verify_api_cert] ? :verify_peer : :verify_none
      chef_opts[:ssl_ca_path]          = opts[:ssl][:ca_path] if opts[:ssl][:ca_path]
      chef_opts[:ssl_ca_file]          = opts[:ssl][:ca_file] if opts[:ssl][:ca_file]
      chef_opts[:ssl_client_cert]      = opts[:ssl][:client_cert] if opts[:ssl][:client_cert]
      chef_opts[:ssl_client_key]       = opts[:ssl][:client_key] if opts[:ssl][:client_key]
      chef_opts[:version_class]        = opts[:version_class] if opts[:version_class]
      # chef/http/ssl_policies.rb reads only from Chef::Config and not from the opts in the constructor
      Chef::Config[:verify_api_cert] = chef_opts[:verify_api_cert]
      Chef::Config[:ssl_verify_mode] = chef_opts[:ssl_verify_mode]
      super(opts[:server_url].to_s, **chef_opts)
    end

    # for compat with Ridley::Connection
    def server_url
      url
    end

    def get(url)
      super(url)
    rescue Net::HTTPExceptions => e
      case e.response.code
      when "404"
        raise Berkshelf::APIClient::ServiceNotFound, "service not found at: #{url}"
      when /^5/
        raise Berkshelf::APIClient::ServiceUnavailable, "service unavailable at: #{url}"
      else
        raise Berkshelf::APIClient::BadResponse, "bad response #{e.response}"
      end
    rescue Errno::ETIMEDOUT, Timeout::Error
      raise Berkshelf::APIClient::TimeoutError, "Unable to connect to: #{url}"
    rescue Errno::EHOSTUNREACH, Errno::ECONNREFUSED => e
      raise Berkshelf::APIClient::ServiceUnavailable, e
    end

    module ClassMethods
      def new_client(**opts, &block)
        client = new(**opts)
        yield client
        # ensure
        # FIXME: does Chef::HTTP support close anywhere?  this will just leak open fds
      end
    end

    def self.included(klass)
      klass.extend ClassMethods
    end

  end

  # This is for simple HTTP
  class RidleyCompatSimple < ::Chef::ServerAPI
    use Chef::HTTP::Decompressor
    use Chef::HTTP::CookieManager
    use Chef::HTTP::ValidateContentLength

    include RidleyCompatAPI
  end

  # This is for JSON-REST
  class RidleyCompatJSON < ::Chef::HTTP::SimpleJSON
    use Chef::HTTP::JSONInput
    use Chef::HTTP::JSONOutput
    use Chef::HTTP::CookieManager
    use Chef::HTTP::Decompressor
    use Chef::HTTP::RemoteRequestID
    use Chef::HTTP::ValidateContentLength

    include RidleyCompatAPI
  end

  # RidleyCompat is the ServerAPI, but we inherit from Chef::HTTP::Simple and then include all our middlewares
  # and then need to include our own CompatAPI.  The end result is a ridley-esque way of talking to a chef server.
  class RidleyCompat < ::Chef::HTTP::Simple
    use Chef::HTTP::JSONInput
    use Chef::HTTP::JSONOutput
    use Chef::HTTP::CookieManager
    use Chef::HTTP::Decompressor
    use Chef::HTTP::Authenticator
    use Chef::HTTP::RemoteRequestID
    use Chef::HTTP::APIVersions if defined?(Chef::HTTP::APIVersions)
    use Chef::HTTP::ValidateContentLength

    include RidleyCompatAPI

    def initialize(**opts)
      opts[:version_class] = Chef::CookbookManifestVersions
      super(**opts)
    end

  end
end
