#-------------------------------------------------------------------------
# Copyright 2013 Microsoft Open Technologies, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#--------------------------------------------------------------------------
require 'azure/core/http/http_response'
require 'azure/core/http/http_request'

# Represents an HTTP request that can perform synchronous queries to
# an HTTP server, returning a HttpResponse
module Azure
  module BaseManagement
    class ManagementHttpRequest < Azure::Core::Http::HttpRequest
      include Azure::Core::Utility
      attr_accessor :warn

      # Creates the ManagementHttpRequest
      #
      # @param method           [Symbol] The HTTP method to use (:get, :post, :put, :del, etc...)
      # @param path             [URI] The URI of the HTTP endpoint to query
      # @param options_or_body  [Hash|IO|String] The request options including {:client, :body} or raw body only
      def initialize(method, path, options_or_body = {})
        options ||= unless options_or_body.is_a?(Hash)
                    {body: options_or_body}
                  end || options_or_body || {}
        options[:client] ||= Azure
        super(method, self.class.request_uri(path, options[:client]), options)
        @warn = options.fetch(:warn, false)
        content_length = body ? body.bytesize.to_s : '0'
        headers.update({
                            'x-ms-version' => '2015-04-01',
                            'Content-Type' => 'application/xml',
                            'Content-Length' => content_length
                        })
      end

      # Public: Sends a request to HTTP server and returns a HttpResponse
      #
      # Returns a Nokogiri::XML instance of HttpResponse body
      def call
        conn = http_setup
        res = set_up_response(method.to_sym, uri, conn, headers ,body)
        response = wait_for_completion(Azure::Core::Http::HttpResponse.new(res))
        Nokogiri::XML response.body unless response.nil?
      end

      def apply_body_headers
        super
      end

      # Public: Wait for HTTP request completion.
      #
      # ==== Attributes
      #
      # * +response+       - Azure::Core::Http::HttpResponse. HttpResponse Response
      #
      # Print Error or Success of HttpRequest
      def wait_for_completion(response)
        ret_val = Nokogiri::XML response.body
        if ret_val.at_css('Error Code') && ret_val.at_css('Error Code').content == 'AuthenticationFailed'
          Azure::Loggerx.error_with_exit ret_val.at_css('Error Code').content + ' : ' + ret_val.at_css('Error Message').content
        end
        if response.status_code.to_i == 200 || response.status_code.to_i == 201
          response
        elsif redirected? response
          rebuild_request response
        elsif response.status_code.to_i > 201 && response.status_code.to_i <= 299
          check_completion(response.headers['x-ms-request-id'])
        elsif warn && !response.success?
          response
        elsif response.body
          if ret_val.at_css('Error Code') && ret_val.at_css('Error Message')
            Azure::Loggerx.error_with_exit ret_val.at_css('Error Code').content + ' : ' + ret_val.at_css('Error Message').content
          else
            Azure::Loggerx.exception_message "http error: #{response.status_code}"
          end
        else
          Azure::Loggerx.exception_message "http error: #{response.status_code}"
        end
      end

      def http_setup
        http = super
        http.ssl[:client_cert] = @client.http_certificate_key if @client.http_certificate_key
        http.ssl[:client_key] = @client.http_private_key if @client.http_private_key
        http
      end

      # Public: Gets the status of the specified operation and determines whether
      # the operation has succeeded, failed, or is still in progress.
      #
      # ==== Attributes
      #
      # * +request_id+       - String.  x-ms-request-id response header of request
      #
      # See: http://msdn.microsoft.com/en-us/library/azure/ee460783.aspx
      #
      # Print Error or Success of Operation.
      def check_completion(request_id)
        request_path = "/#{client.subscription_id}/operations/#{request_id}"
        conn = http_setup
        headers['Content-Length'] = '0'
        @method = :get
        done = false
        until done
          Azure::Loggerx.info('# ')
          res = set_up_response(method.to_sym, URI(request_path), conn, headers ,body)
          response = Azure::Core::Http::HttpResponse.new(res)
          ret_val = Nokogiri::XML response.body
          status = xml_content(ret_val, 'Operation Status')
          status_code = response.status_code.to_i
          if status != 'InProgress'
            done = true
          end
          if redirected? response
            @uri = self.class.request_uri(response.headers['location'], client)
            conn = http_setup
            done = false
          end
          if done
            if status.downcase != 'succeeded'
              error_code = xml_content(ret_val, 'Operation Error Code')
              error_msg = xml_content(ret_val, 'Operation Error Message')
              Azure::Loggerx.exception_message "#{error_code}: #{error_msg}"
            else
              Azure::Loggerx.success "#{status.downcase} (#{status_code})"
            end
            return
          else
            sleep(5)
          end
        end
      end

      def rebuild_request(response)
        host_uri = URI.parse(response.headers['location'])
        conn = http_setup
        res = set_up_response(method.to_sym, host_uri, conn, headers ,body)
        wait_for_completion(HttpResponse.new(res))
      end

      def redirected?(response)
        (response.status_code.to_i == 307)
      end

      private

      def self.request_uri(path, client)
        relative_path = path =~ /^\// ? path[1..-1] : path # remove the starting slash in the relative path if exists
        URI.join(client.management_endpoint, '/' + client.subscription_id + '/', relative_path)
      end
    end
  end
end
