require "net/http"

module Kloudless
  # Net::HTTP wrapper
  class HTTP
    # Public: Headers global to all requests
    def self.headers
      @headers ||= {}
    end

    def self.get(path, params: {}, headers: {})
      uri = URI.parse(Kloudless::API_URL + path)
      uri.query = URI.encode_www_form(params) if !params.empty?

      request = Net::HTTP::Get.new(uri)
      request.initialize_http_header(headers)

      execute(request)
    end

    # TODO: decouple Kloudless::HTTP methods from #execute. Have methods return
    # request object, defaults to json parsing, but allow the option for raw
    def self.get_raw(path)
      uri = URI.parse(Kloudless::API_URL + path)
      request = Net::HTTP::Get.new(uri)
      request.initialize_http_header(headers)

      execute(request, parse_json: false)
    end


    def self.post(path, params: {}, headers: {})
      uri = URI.parse(Kloudless::API_URL + path)
      headers["Content-Type"] = "application/json"

      request = Net::HTTP::Post.new(uri)
      request.initialize_http_header(headers)
      request.set_form_data(params) if !params.empty?

      execute(request)
    end

    def self.patch(path, params: {}, headers: {})
      uri = URI.parse(Kloudless::API_URL + path)
      headers["Content-Type"] = "application/json"

      request = Net::HTTP::Post.new(uri)
      request.initialize_http_header(headers)
      request.set_form_data(params) if !params.empty?

      execute(request)
    end

    def self.delete(path, params: {}, headers: {})
      uri = URI.parse(Kloudless::API_URL + path)
      uri.query = URI.encode_www_form(params) if !params.empty?

      request = Net::HTTP::Delete.new(uri)
      request.initialize_http_header(headers)

      execute(request)
    end

    def self.execute(request, parse_json: true)
      uri = request.uri
      @last_request = request
      headers.each {|k,v| request[k] = v}

      response = @mock_response || Net::HTTP.start(uri.hostname, uri.port, use_ssl: (uri.scheme == "https")) {|http|
        http.request(request)
      }

      if parse_json
        json = JSON.parse(response.body)
        raise Kloudless::Error.from_json(json) if json["error_code"]
        json
      else
        response
      end
    end

    # Internal: Returns `response` the next time #execute is invoked for the
    # duration of `blk`. Used for testing.
    #
    #  mock = Struct.new(:body).new("{}")
    #  Kloudless::HTTP.mock_response(mock) do
    #    Kloudless::HTTP.get "/foo"  # returns {}
    #  end
    def self.mock_response(response = nil, &blk)
      @mock_response = response || Struct.new(:body).new("{}")
      blk.call
    ensure
      @mock_response = nil
    end

    require 'minitest/mock'
    def self.expect(method, returns: nil, args: [], &blk)
      begin
        mock = MiniTest::Mock.new
        Kloudless.http = mock
        mock.expect(method, returns, args)
        blk.call
        mock.verify
      ensure
        Kloudless.http = Kloudless::HTTP
      end
    end

    # Internal: Returns the last Net::HTTP request sent by #execute.
    def self.last_request
      @last_request
    end
  end
end