require 'bbq/core/roles'

module Bbq
  module Core
    class TestClient
      class UnsupportedMethodError < StandardError; end

      include Bbq::Core::Roles

      def initialize(options = {})
        @options = options
      end

      HTTP_METHODS = %w(get post put delete head options patch)

      HTTP_METHODS.each do |method|
        class_eval <<-RUBY
          def #{method}(path, params = {}, headers = {})
            unless driver.respond_to? :#{method}
              raise UnsupportedMethodError, "Your driver does not support #{method.upcase} method"
            end

            response = driver.#{method}(path, params, default_headers.merge(headers))
            parsed_response = parse_response(response)
            yield parsed_response if block_given?
            parsed_response
          end
        RUBY
      end

      protected
      def app
        @options[:app] || Bbq::Core.app
      end

      def default_headers
        @options[:headers] || {}
      end

      def driver
        @driver ||= RackTest.new(app)
      end

      def parse_response(response)
        case response.headers["Content-Type"]
        when /^application\/(.*\+)?json/
          response.extend(JsonBody)
        when /^application\/(.*\+)?x-yaml/
          response.extend(YamlBody)
        else
          response
        end
      end

      class RackTest
        attr_accessor :app

        def initialize(app)
          self.app = app
        end

        module ConvertHeaders
          HTTP_METHODS.each do |method|
            class_eval <<-RUBY
              def #{method}(path, params = {}, headers = {})
                super(path, params, to_env_headers(headers))
              end
            RUBY
          end

          def to_env_headers(http_headers)
            http_headers.map do |k, v|
              k = k.upcase.gsub("-", "_")
              k = "HTTP_#{k}" unless ["CONTENT_TYPE", "CONTENT_LENGTH"].include?(k)
              { k => v }
            end.inject({}, :merge)
          end
        end

        include Rack::Test::Methods
        include ConvertHeaders
      end

      module JsonBody
        def body
          @parsed_body ||= super.empty?? super : JSON.parse(super)
        end
      end

      module YamlBody
        def body
          @parsed_body ||= YAML.load(super)
        end
      end
    end
  end
end
