require 'rack/request'
require 'rack/utils'
require 'rack/mock'
require 'mime-types'

module JSONAPIonify::Api
  class Server::Request < Rack::Request
    def self.env_for(url, method, options = {})
      options[:method] = method
      new Rack::MockRequest.env_for(url, options)
    end

    def headers
      env_headers                 = env.select do |name, _|
        name.start_with?('HTTP_') && !%w{HTTP_VERSION}.include?(name)
      end.each_with_object({}) do |(name, value), hash|
        hash[name[5..-1].gsub('_', '-').downcase] = value
      end
      env_headers['content-type'] = content_type if content_type
      Rack::Utils::HeaderHash.new(env_headers)
    end

    def http_string
      # GET /path/file.html HTTP/1.0
      # From: someuser@jmarshall.com
      # User-Agent: HTTPTool/1.0
      # [blank line here]
      [].tap do |lines|
        lines << "#{request_method} #{fullpath} HTTP/1.1"
        headers.each do |k, v|
          lines << "#{k.split('-').map(&:capitalize).join('-')}: #{v}"
        end
        lines << ''
        lines << pretty_body if has_body?
      end.join("\n")
    end

    def pretty_body
      return '' unless has_body?
      value = body.read
      JSON.pretty_generate(Oj.load(value))
    rescue Oj::ParseError
      value
    ensure
      body.rewind
    end

    def extension
      ext = File.extname(path)
      return nil unless ext
      ext = ext[0] == '.' ? ext[1..-1] : ext
      ext.to_s.empty? ? nil : ext
    end

    def content_type
      super.try(:split, ';').try(:first)
    end

    def accept_params
      @accept_params ||= begin
        ext_mime = MIME::Types.type_for(path)[0]&.content_type
        accepts  = (headers['accept'] || ext_mime || '*/*').split(',')
        types    = [ext_mime].compact | accepts
        types.each_with_object({}) do |type, list|
          list[Server::MediaType.type(type)] = Server::MediaType.params(type)
        end
      end
    end

    def jsonapi_params
      accept_params['application/vnd.api+json'] || {}
    end

    def accept
      @accept ||= accept_params.keys
    end

    def authorizations
      parts = headers['authorization'].to_s.split(' ')
      parts.length == 2 ? Rack::Utils::HeaderHash.new([parts].to_h) : {}
    end

    def has_body?
      body.read(1).present?
    ensure
      body.rewind
    end

    def root_url
      URI.parse(url).tap do |uri|
        uri.query = nil
        uri.path.chomp! path_info
      end.to_s
    end
  end
end