#lang racket/base
(require racket/contract
         web-server/dispatchers/dispatch
         web-server/servlet/servlet-structs)

(define tester/c
  (->* ()
       ((or/c string? url? request? false/c)
        (listof binding?)
        #:raw? boolean?
        #:headers? boolean?)
       (or/c bytes?
             xexpr?
             (cons/c bytes?
                     (or/c bytes?
                           xexpr?)))))

(provide/contract
 [tester/c contract?]
 [make-servlet-tester
  (-> (-> request?
          can-be-response?)
      tester/c)]
 [make-dispatcher-tester
  (-> dispatcher/c
      tester/c)])

;; Real Library
(require racket/list
         racket/promise
         net/url
         web-server/http
         web-server/servlet-dispatch)

(define (make-servlet-tester servlet)
  (define d (dispatch/servlet servlet))
  (make-dispatcher-tester d))

(define (make-dispatcher-tester d)
  (λ ([s-or-u-or-req #f]
      [bs empty]
      #:raw? [raw? #f]
      #:headers? [hs? #f])
    (define req
      (if (request? s-or-u-or-req)
        s-or-u-or-req
        (let ()
          (define s-or-u
            (if s-or-u-or-req
              s-or-u-or-req
              "/"))
          (define u
            (if (string? s-or-u)
              (string->url s-or-u)
              s-or-u))
          (make-request #"GET" u empty (delay bs) #"" "127.0.0.1" 80 "127.0.0.1"))))
    (call d req #:raw? raw? #:headers? hs?)))

;; Intermediate Library
(require racket/match
         xml
         web-server/private/timer
         web-server/private/connection-manager
         web-server/private/web-server-structs)

(define (call d req #:raw? raw? #:headers? hs?)
  (htxml (collect d req) raw? hs?))
(define (htxml bs raw? hs?)
  (match (regexp-match #"^(.+?)\r\n\r\n(.*)$" bs)
    [(list _ h s)
     (define body
       (if raw?
         s
         (string->xexpr (bytes->string/utf-8 s))))
     (if hs?
       (cons h body)
       body)]
    [_
     (error 'servlet "Servlet did not return an HTTP response, instead returned ~v"
            bs)]))

(define (collect d req)
  (parameterize ([current-custodian (make-custodian)])
    (define-values (c i o) (make-mock-connection #""))
    (parameterize ([current-server-custodian (current-custodian)])
      (call-with-continuation-barrier
       (lambda ()
         (d c req))))
    (redact (get-output-bytes o))))

(define (make-mock-connection ib)
  (define ip (open-input-bytes ib))
  (define op (open-output-bytes))
  (define tm (start-timer-manager))
  (values (make-connection 0 (make-timer tm never-evt +inf.0 (lambda () (void)))
                           ip op (current-custodian) #t)
          ip
          op))

(define (redact b)
  (regexp-replace
   #"Date: [a-zA-Z0-9:, ]+ GMT\r\n"
   (regexp-replace
    #"Last-Modified: [a-zA-Z0-9:, ]+ GMT\r\n"
    b
    #"Last-Modified: REDACTED GMT\r\n")
   #"Date: REDACTED GMT\r\n"))
