module Nyara
  # Test helper
  module Test
    class Response
      # Whether request is success
      def success?
        status < 400
      end

      def redirect_location
        if HTTP_REDIRECT_STATUS.include?(status)
          header['Location']
        end
      end

      # C-ext methods: header, body, status, initialize(data), set_cookies
    end

    Env = Struct.new :cookie, :session, :request, :controller, :response, :response_size_limit
    class Env
      # #### Call-seq
      #
      #     # change size limit of response data to 100M:
      #     @_env = Env.new 10**8
      #
      def initialize response_size_limit=5_000_000
        self.response_size_limit = response_size_limit
        self.cookie = ParamHash.new
        self.session = Session.new
      end

      def process_request_data data
        client, server = Socket.pair :UNIX, :STREAM
        self.request = Ext.request_new
        Ext.request_set_fd request, server.fileno

        client << data
        self.controller = Ext.handle_request request
        # response_data can be empty
        response_data = (client.read_nonblock response_size_limit rescue '')
        self.response = Response.new response_data

        # no env when route not found
        if request.session
          # merge session
          session.clear
          session.merge! request.session

          # merge Set-Cookie
          response.set_cookies.each do |cookie_seg|
            # todo distinguish delete, value and set
            ParamHash.parse_cookie cookie, cookie_seg
          end
        end

      ensure
        Ext.request_unset_fd request
        server.close
        client.close
      end

      def http meth, path, headers={}, body_string_or_hash=''
        headers = (headers || {}).dup

        # serialize body
        # todo build multipart for file
        if body_string_or_hash.is_a?(Hash)
          body = body_string_or_hash.to_param
          headers['Content-Type'] = 'application/x-www-form-urlencoded'
        else
          body = body_string_or_hash.to_s
          headers['Content-Type'] ||= 'text/plain'
        end
        if body.bytesize > 0
          headers['Content-Length'] = body.bytesize
        end

        # serialize cookie / session
        if headers['Cookie']
          cookie.clear
          cookie.merge! Cookie.decode headers
        end
        Session.encode_to_cookie session, cookie
        headers['Cookie'] = Cookie.encode cookie

        request_data = ["#{meth.to_s.upcase} #{Ext.escape path, true} HTTP/1.1\r\n"]
        headers.each do |k, v|
          request_data << "#{k}: #{v}\r\n"
        end
        request_data << "\r\n"
        request_data << body
        process_request_data request_data.join
      end
    end

    def env
      @_env ||= Env.new
    end

    # #### Call-seq
    #
    #     http :trace, '/'
    #
    def http *xs
      env.http *xs
    end

    # #### Call-seq
    #
    #     get '/', headers
    #
    def get path, header={}, body_string_or_hash=""
      env.http 'GET', path, header, body_string_or_hash
    end

    # #### Call-seq
    #
    #     post '/', {}, page: 3
    #     post '/', { 'content-type' => 'application/json' }, '{"page":3}'
    #
    def post path, header={}, body_string_or_hash=""
      env.http 'POST', path, header, body_string_or_hash
    end

    def put path, header={}, body_string_or_hash=""
      env.http 'PUT', path, header, body_string_or_hash
    end

    def delete path, header={}, body_string_or_hash=""
      env.http 'DELETE', path, header, body_string_or_hash
    end

    def patch path, header={}, body_string_or_hash=""
      env.http 'PATCH', path, header, body_string_or_hash
    end

    def options path, header={}, body_string_or_hash=""
      env.http 'OPTIONS', path, header, body_string_or_hash
    end

    def path_to id, *args
      # similar to Controller#path_to, but without local query
      if args.last.is_a?(Hash)
        opts = args.pop
      end

      r = Route.global_path_template(id) % args

      if opts
        format = opts.delete :format
        r << ".#{format}" if format
        r << '?' << opts.to_param unless query.empty?
      end
      r
    end

    def cookie
      env.cookie
    end

    def session
      env.session
    end

    def request
      env.request
    end

    def response
      env.response
    end

    def redirect_location
      env.response.redirect_location
    end

    def follow_redirect
      # todo validate scheme and host
      u = URI.parse(redirect_location)
      path = [u.path, u.query].compact.join '?'
      get path
    end
  end
end
