require 'cgi'

module Seahorse
  module Client
    module Plugins
      # @api private
      class RestfulBindings < Plugin

        # @api private
        class Handler < Client::Handler

          include Seahorse::Model::Shapes

          def call(context)
            build_request(context)
            @handler.call(context).on(200..299) do |response|
              parse_response(response)
            end
          end

          private

          # Populates the HTTP request method and headers.
          def build_request(context)
            populate_http_request_method(context)
            populate_http_headers(context)
          end

          def populate_http_request_method(context)
            context.http_request.http_method = context.operation.http_method
          end

          def populate_http_headers(context)
            params = context.params
            headers = context.http_request.headers
            each_member(context.operation.input) do |member_name, member_ref|
              value = params[member_name]
              next if value.nil?
              case member_ref.location
              when 'header'  then serialize_header(headers, member_ref, value)
              when 'headers' then serialize_header_map(headers, member_ref, value)
              end
            end
          end

          def serialize_header(headers, ref, value)
            headers[ref.location_name] = serialize_header_value(ref, value)
          end

          def serialize_header_map(headers, ref, values)
            prefix = ref.location_name || ''
            values.each_pair do |name, value|
              value = serialize_header_value(ref.shape.value, value)
              headers["#{prefix}#{name}"] = value
            end
          end

          def serialize_header_value(ref, value)
            if TimestampShape === ref.shape
              value.utc.httpdate
            else
              value.to_s
            end
          end

          # Extracts HTTP response headers and status code.
          def parse_response(response)
            headers = response.context.http_response.headers
            each_member(response.context.operation.output) do |key, ref|
              case ref.location
              when 'statusCode'
                status_code = response.context.http_response.status_code
                response.data[key] = status_code
              when 'header'
                if headers.key?(ref.location_name)
                  response.data[key] = extract_header(headers, ref)
                end
              when 'headers'
                response.data[key] = extract_header_map(headers, ref)
              end
            end
          end

          def extract_header(headers, ref)
            parse_header_value(ref, headers[ref.location_name])
          end

          def extract_header_map(headers, ref)
            prefix = ref.location_name || ''
            hash = {}
            headers.each do |header, value|
              if match = header.match(/^#{prefix}(.+)/i)
                hash[match[1]] = parse_header_value(ref.shape.value, value)
              end
            end
            hash
          end

          def parse_header_value(ref, value)
            if value
              case ref.shape
              when IntegerShape then value.to_i
              when FloatShape then value.to_f
              when BooleanShape then value == 'true'
              when TimestampShape
                if value =~ /\d+(\.\d*)/
                  Time.at(value.to_f)
                else
                  Time.parse(value)
                end
              else value
              end
            end
          end

          def each_member(ref, &block)
            ref.shape.members.each(&block) if ref
          end

        end

        handle(Handler, priority: 90)

      end
    end
  end
end
