require 'net/http'
require 'time'
require 'securerandom'
require 'uri'
require 'json'
require 'logger'

require 'aliyun'
require 'aliyun/utils'
require 'aliyun/signature_methods'

module Aliyun
  module Services
    class Base
      attr_accessor :access_key_id, :access_key_secret

      def initialize(options)
        self.access_key_id = options[:access_key_id]
        self.access_key_secret = options[:access_key_secret]
      end

      def method_missing(method, *args, &block)
        if service_actions.include?(action = method.to_sym)
          params = args[0].nil? ? {} : args[0]
          send_request(action, params)
        else
          super
        end
      end

      def base_url
        "#{request_schema}://#{request_host}"
      end

      def secure?
        request_schema == 'https'
      end

      def name
        self.class::NAME
      end

      def request_host
        service_definition[:host]
      end

      def request_schema
        service_definition[:schema] || Aliyun.config.default_schema || 'https'
      end

      def response_format
        service_definition[:format] || Aliyun.config.default_format || 'JSON'
      end

      def api_version
        service_definition[:version]
      end

      def separator
        "&"
      end

      def http_method
        'GET'
      end

      def signature_method
        Aliyun::SignatureMethods[service_definition[:signature_method]]
      end

      private

      def send_request(action, params)
        uri = URI(base_url)
        uri.query = URI.encode_www_form(generate_request_params(action, params))

        http = Net::HTTP.new(uri.host, uri.port)
        http.use_ssl = secure?
        http.verify_mode = OpenSSL::SSL::VERIFY_NONE

        request = Net::HTTP::Get.new(uri.request_uri)
        logger.debug uri.request_uri
        response = http.request(request)

        case response
        when Net::HTTPSuccess
          return process_data(action, response.body)
        else
          raise process_error(action, response.code, response.body)
        end
      end

      def generate_request_params(action, params)
        params = process_params(action, params)
        logger.debug params
        params = params.merge(default_params(action))
        logger.debug params
        params['Signature'] = signature_method.generate(params, self)
        logger.debug params
        params
      end

      def process_params(action, params)
        param_definitions = service_action_definitions[action][:parameters]
        params.reduce({}) do |new_params, (k,v)|
          unless param_definitions[k].nil?
            new_params[param_definitions[k][:parameter]] = v
          end
          new_params
        end
      end

      def process_data(action, data)
        JSON.parse(data)
      end

      def process_error(action, error_code, error_message)
        Aliyun::Errors::Base.new("Response code: #{error_code}, message: #{error_message}")
      end

      def default_params(action)
        {
          'Format'            => response_format,
          'Version'           => api_version,
          'Action'            => service_action_definitions[action][:action],
          'AccessKeyId'       => access_key_id,
          'Timestamp'         => Time.now.utc.iso8601,
          'SignatureMethod'   => signature_method.name,
          'SignatureVersion'  => signature_method.version,
          'SignatureNonce'    => SecureRandom.uuid
        }
      end

      def service_definition
        self.class.load_service_definition(name)
      end

      def service_actions
        service_action_definitions.keys
      end

      def service_action_definitions
        self.class.load_service_action_definitions(name)
      end

      def logger
        Aliyun.config.logger || Logger.new(STDOUT)
      end

      def self.load_service_definition(service_name)
        @@service_definition ||= {}
        @@service_definition[service_name] ||= Aliyun::Utils.symbolize_hash_keys(
          YAML::load_file("#{api_definition_dir}/#{service_name}.yml")
        )
      end


      def self.load_service_action_definitions(service_name)
        @@service_action_definitions ||= {}
        @@service_action_definitions[service_name] ||=  Dir["#{api_definition_dir}/#{service_name}/*.yml"].map do |file|
                                                          File.basename(file, '.yml')
                                                        end.reduce({}) do |actions, action_name|
                                                          actions[action_name.to_sym] = Aliyun::Utils.symbolize_hash_keys(
                                                            YAML::load_file("#{api_definition_dir}/#{service_name}/#{action_name}.yml")
                                                          )
                                                          actions
                                                        end
      end
    end
  end
end
