require 'pact/retry'
require 'pact/hal/authorization_header_redactor'
require 'net/http'
require 'rack'
require 'openssl'

module Pact
  module Hal
    class HttpClient
      attr_accessor :username, :password, :verbose, :token

      def initialize options
        @username = options[:username]
        @password = options[:password]
        @verbose = options[:verbose]
        @token = options[:token]
      end

      def get href, params = {}, headers = {}
        uri = URI(href)
        if params && params.any?
          existing_params = Rack::Utils.parse_nested_query(uri.query)
          uri.query = Rack::Utils.build_nested_query(existing_params.merge(params))
        end
        perform_request(create_request(uri, 'Get', nil, headers), uri)
      end

      def put href, body = nil, headers = {}
        uri = URI(href)
        perform_request(create_request(uri, 'Put', body, headers), uri)
      end

      def post href, body = nil, headers = {}
        uri = URI(href)
        perform_request(create_request(uri, 'Post', body, headers), uri)
      end

      def create_request uri, http_method, body = nil, headers = {}
        request = Net::HTTP.const_get(http_method).new(uri.request_uri)
        headers.each do | key, value |
          request[key] = value
        end
        request.body = body if body
        request.basic_auth username, password if username
        request['Authorization'] = "Bearer #{token}" if token
        request
      end

      def perform_request request, uri
        response = Retry.until_true do
          http = Net::HTTP.new(uri.host, uri.port, :ENV)
          http.set_debug_output(output_stream) if verbose?
          http.use_ssl = (uri.scheme == 'https')
          http.ca_file = ENV['SSL_CERT_FILE'] if ENV['SSL_CERT_FILE'] && ENV['SSL_CERT_FILE'] != ''
          http.ca_path = ENV['SSL_CERT_DIR'] if ENV['SSL_CERT_DIR'] && ENV['SSL_CERT_DIR'] != ''

          if x509_certificate?
            http.cert = OpenSSL::X509::Certificate.new(x509_client_cert_file)
            http.key = OpenSSL::PKey::RSA.new(x509_client_key_file)
          end

          if disable_ssl_verification?
            if verbose?
              Pact.configuration.output_stream.puts("SSL verification is disabled")
            end
            http.verify_mode = OpenSSL::SSL::VERIFY_NONE
          end
          http.start do |http|
            http.request request
          end
        end
        Response.new(response)
      end

      def output_stream
        AuthorizationHeaderRedactor.new(Pact.configuration.output_stream)
      end

      def verbose?
        verbose || ENV['VERBOSE'] == 'true'
      end

      def x509_certificate?
        ENV['X509_CLIENT_CERT_FILE'] && ENV['X509_CLIENT_CERT_FILE'] != '' &&
          ENV['X509_CLIENT_KEY_FILE'] && ENV['X509_CLIENT_KEY_FILE'] != ''
      end

      def x509_client_cert_file
        File.read(ENV['X509_CLIENT_CERT_FILE'])
      end

      def x509_client_key_file
        File.read(ENV['X509_CLIENT_KEY_FILE'])
      end

      def disable_ssl_verification?
        ENV['PACT_DISABLE_SSL_VERIFICATION'] == 'true' || ENV['PACT_BROKER_DISABLE_SSL_VERIFICATION'] == 'true'
      end

      class Response < SimpleDelegator
        def body
          bod = raw_body
          if bod && bod != ''
            JSON.parse(bod)
          else
            nil
          end
        end

        def raw_body
          __getobj__().body
        end

        def status
          code.to_i
        end

        def success?
          __getobj__().code.start_with?("2")
        end

        def json?
          self['content-type'] && self['content-type'] =~ /json/
        end
      end
    end
  end
end
