require 'net/http'
require 'net/https'
require 'stringio'

class StubSocket #:nodoc:

  def initialize(*args)
  end

  def closed?
    @closed ||= true
  end

  def readuntil(*args)
  end

end

module StubResponse
  def read_body(*args, &block)
    yield @body if block_given?
    @body
  end
end

module Net  #:nodoc: all

  class BufferedIO
    def initialize_with_webmock(io, debug_output = nil)
      @read_timeout = 60
      @rbuf = ''
      @debug_output = debug_output

      @io = case io
      when Socket, OpenSSL::SSL::SSLSocket, IO
        io
      when String
        StringIO.new(io)
      end
      raise "Unable to create local socket" unless @io
    end
    alias_method :initialize_without_webmock, :initialize
    alias_method :initialize, :initialize_with_webmock
  end

  class HTTP
    class << self
      def socket_type_with_webmock
        StubSocket
      end
      alias_method :socket_type_without_webmock, :socket_type
      alias_method :socket_type, :socket_type_with_webmock
    end

    def request_with_webmock(request, body = nil, &block)
      request_signature = WebMock::NetHTTPUtility.request_signature_from_request(self, request, body)

      WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)

      if WebMock.registered_request?(request_signature)
        @socket = Net::HTTP.socket_type.new
        webmock_response = WebMock.response_for_request(request_signature)
        build_net_http_response(webmock_response, &block)
      elsif WebMock.net_connect_allowed?(request_signature.uri)
        connect_without_webmock
        request_without_webmock(request, nil, &block)
      else
        raise WebMock::NetConnectNotAllowedError.new(request_signature)
      end
    end
    alias_method :request_without_webmock, :request
    alias_method :request, :request_with_webmock


    def connect_with_webmock
      unless @@alredy_checked_for_right_http_connection ||= false
        WebMock::NetHTTPUtility.puts_warning_for_right_http_if_needed
        @@alredy_checked_for_right_http_connection = true
      end
      nil
    end
    alias_method :connect_without_webmock, :connect
    alias_method :connect, :connect_with_webmock

    def build_net_http_response(webmock_response, &block)
      response = Net::HTTPResponse.send(:response_class, webmock_response.status[0].to_s).new("1.0", webmock_response.status[0].to_s, webmock_response.status[1])
      response.instance_variable_set(:@body, webmock_response.body)
      webmock_response.headers.to_a.each { |name, value| response[name] = value }

      response.instance_variable_set(:@read, true)

      response.extend StubResponse

      raise Timeout::Error, "execution expired" if webmock_response.should_timeout

      webmock_response.raise_error_if_any

      yield response if block_given?

      response
    end

  end

end

module WebMock
  module NetHTTPUtility

    def self.request_signature_from_request(net_http, request, body = nil)
      protocol = net_http.use_ssl? ? "https" : "http"

      path = request.path
      path = Addressable::URI.heuristic_parse(request.path).request_uri if request.path =~ /^http/

      if request["authorization"] =~ /^Basic /
        userinfo = WebMock::Util::Headers.decode_userinfo_from_header(request["authorization"])
        userinfo = WebMock::Util::URI.encode_unsafe_chars_in_userinfo(userinfo) + "@"
      else
        userinfo = ""
      end

      uri = "#{protocol}://#{userinfo}#{net_http.address}:#{net_http.port}#{path}"
      method = request.method.downcase.to_sym

      headers = Hash[*request.to_hash.map {|k,v| [k, v]}.inject([]) {|r,x| r + x}]

      headers.reject! {|k,v| k =~ /[Aa]uthorization/ && v.first =~ /^Basic / } #we added it to url userinfo


      if request.body_stream
        body = request.body_stream.read
        request.body_stream = nil
      end

      if body != nil && body.respond_to?(:read)
        request.set_body_internal body.read
      else
        request.set_body_internal body
      end

      WebMock::RequestSignature.new(method, uri, :body => request.body, :headers => headers)
    end


    def self.check_right_http_connection
      @was_right_http_connection_loaded = defined?(RightHttpConnection)
    end

    def self.puts_warning_for_right_http_if_needed
      if !@was_right_http_connection_loaded && defined?(RightHttpConnection)
        $stderr.puts "\nWarning: RightHttpConnection has to be required before WebMock is required !!!\n"
      end
    end

  end
end

WebMock::NetHTTPUtility.check_right_http_connection