# frozen_string_literal: true

require "faraday"
require "securerandom"
require "active_support/all"
require "net/http"

module Net::HTTPHeader
  def capitalize(name)
    name
  end
  private :capitalize
end

module Aliyun
  module Connector
    class ROAClient
      attr_accessor :endpoint, :api_version, :access_key_id,
                    :access_key_secret, :security_token, :hostname, :opts

      def initialize(config)
        validate config

        self.endpoint          = config[:endpoint]
        self.api_version       = config[:api_version]
        self.access_key_id     = config[:access_key_id] || Aliyun.access_key_id
        self.access_key_secret = config[:access_key_secret] || Aliyun.access_key_secret
        self.security_token    = config[:security_token]
      end

      def request(method:, uri:, params: {}, body: {}, headers: {}, options: {})
        # :"Content-Type" => "application/json" to "content-type" => "application/json"
        headers.deep_transform_keys! { |key| key.to_s.downcase }
        mix_headers = default_headers.merge(headers)

        response = connection.send(method.downcase) do |request|
          request.url uri, params
          if body
            if mix_headers["content-type"].start_with? "application/json"
              request_body = body.to_json
            elsif mix_headers["content-type"].start_with? "application/x-www-form-urlencoded"
              request_body = URI.encode_www_form(body)
            else
              request_body = body
            end
            mix_headers["content-md5"]    = Digest::MD5.base64digest request_body
            mix_headers["content-length"] = request_body.length.to_s
            request.body                  = request_body
          end
          string2sign                 = string_to_sign(method, uri, mix_headers, params)
          mix_headers[:authorization] = authorization(string2sign)
          mix_headers.each { |key, value| request.headers[key] = value }
        end

        return response if options.has_key? :raw_body

        response_content_type = response.headers["Content-Type"] || ""
        if response_content_type.start_with?("application/json")
          if response.status >= 400
            result = JSON.parse(response.body)
            raise StandardError, "code: #{response.status}, #{result['Message']} requestid: #{result['RequestId']}"
          end
        end

        if response_content_type.start_with?("text/xml")
          result = Hash.from_xml(response.body)
          raise ACSError, result["Error"] if result["Error"]
        end

        response
      end

      def connection(adapter = Faraday.default_adapter)
        Faraday.new(url: self.endpoint) { |f| f.adapter adapter }
      end

      def get(uri: "", headers: {}, params: {}, options: {})
        request(method: :get, uri: uri, params: params, body: {}, headers: headers, options: options)
      end

      def post(uri: "", headers: {}, params: {}, body: {}, options: {})
        request(method: :post, uri: uri, params: params, body: body, headers: headers, options: options)
      end

      def put(uri: "", headers: {}, params: {}, body: {}, options: {})
        request(method: :put, uri: uri, params: params, body: body, headers: headers, options: options)
      end

      def delete(uri: "", headers: {}, params: {}, options: {})
        request(method: :delete, uri: uri, params: params, body: {}, headers: headers, options: options)
      end

      def default_headers
        default_headers = {
          "accept"                  => "application/json",
          "date"                    => Time.now.httpdate,
          "host"                    => URI(self.endpoint).host,
          "x-acs-signature-nonce"   => SecureRandom.hex(16),
          "x-acs-signature-method"  => "HMAC-SHA1",
          "x-acs-signature-version" => "1.0",
          "x-acs-version"           => self.api_version,
          "x-sdk-client"            => "RUBY(#{RUBY_VERSION})", # FIXME: 如何获取Gem的名称和版本号
          "user-agent" => DEFAULT_UA
        }
        if self.security_token
          default_headers["x-acs-accesskey-id"]   = self.access_key_id
          default_headers["x-acs-security-token"] = self.security_token
        end
        default_headers
      end

      private
        def string_to_sign(method, uri, headers, query = {})
          header_string = [
            method,
            headers["accept"],
            headers["content-md5"] || "",
            headers["content-type"] || "",
            headers["date"],
          ].join("\n")
          "#{header_string}\n#{canonicalized_headers(headers)}#{canonicalized_resource(uri, query)}"
        end

        def canonicalized_headers(headers)
          headers.keys.select { |key| key.to_s.start_with? "x-acs-" }
                 .sort.map { |key| "#{key}:#{headers[key].strip}\n" }.join
        end

        def canonicalized_resource(uri, query_hash = {})
          query_string = query_hash.sort.map { |key, value| "#{key}=#{value}" }.join("&")
          query_string.empty? ? uri : "#{uri}?#{query_string}"
        end

        def authorization(string_to_sign)
          "acs #{self.access_key_id}:#{signature(string_to_sign)}"
        end

        def signature(string_to_sign)
          Base64.encode64(OpenSSL::HMAC.digest("sha1", self.access_key_secret, string_to_sign)).strip
        end

        def validate(config)
          raise ArgumentError, 'must pass "config"' unless config
          raise ArgumentError, 'must pass "config[:endpoint]"' unless config[:endpoint]
          unless config[:endpoint].start_with?("http://") || config[:endpoint].start_with?("https://")
            raise ArgumentError, '"config.endpoint" must starts with \'https://\' or \'http://\'.'
          end
          raise ArgumentError, 'must pass "config[:api_version]"' unless config[:api_version]
          raise ArgumentError, 'must pass "config[:access_key_id]"' unless config[:access_key_id]
          raise ArgumentError, 'must pass "config[:access_key_secret]"' unless config[:access_key_secret]
        end

        class ACSError < StandardError
          attr_accessor :code

          def initialize(error)
            self.code  = error["Code"]
            message    = error["Message"]
            host_id    = error["HostId"]
            request_id = error["RequestId"]
            super("#{message} host_id: #{host_id}, request_id: #{request_id}")
          end
        end
    end
  end
end