#lang racket/base
(require rackunit
         racket/system
         racket/match
         (for-syntax racket/base
                     syntax/parse)
         racket/file
         racket/runtime-path
         racket/path
         racket/list
         racket/format
         racket/port
         racket/string
         setup/dirs
         "shelly.rkt"
         "git-http-proxy.rkt")

(define-runtime-path test-source-directory ".")

;; Use a consistent directory, so that individual tests can be
;; run after "tests-create.rkt":
(define-runtime-path test-directory (build-path (find-system-path 'temp-dir)
                                                "pkg-test-work"))

(define (sync-test-directory)
  (printf "Syncing test directory\n")
  (make-directory* test-directory)
  (parameterize ([current-directory test-source-directory])
    (for ([f (in-directory)])
      (define src f)
      (define dest (build-path test-directory f))
      (cond
       [(directory-exists? src) (make-directory* dest)]
       [else (copy-file src dest #t)]))))

(define-syntax-rule (this-test-is-run-by-the-main-test)
  (module test racket/base))

(define (get-info-domain-cache-path)
  (define c (first (current-library-collection-paths)))
  (define p (build-path c "info-domain" "compiled" "cache.rktd"))
  (and (file-exists? p)
       p))

(define fake-installation-dir (make-parameter #f))

(define (with-fake-installation* t #:default-scope [default-scope "installation"])
  (define tmp-dir
    (make-temporary-file ".racket.fake-installation~a" 'directory
                         (find-system-path 'temp-dir)))
  (make-directory* tmp-dir)
  (dynamic-wind
      void
      (λ ()
         (define ->s path->string)
         (define config
           (hash
            ;; redirect main installation via "share" to
            ;; our temporary directory:
            'share-dir
            (->s (build-path tmp-dir))

            'installation-name
            "test"

            'default-scope
            default-scope

            ;; Find existing links and packages from the
            ;; old configuration:
            'links-search-files 
            (cons #f
                  (map ->s (get-links-search-files)))
            'pkgs-search-dirs
            (cons #f
                  (map ->s (get-pkgs-search-dirs)))))
         (call-with-output-file*
          (build-path tmp-dir "config.rktd")
          (lambda (o)
            (write config o)
            (newline o)))
         (define tmp-dir-s
           (path->string tmp-dir))
         (parameterize ([current-environment-variables
                         (environment-variables-copy
                          (current-environment-variables))]
                        [fake-installation-dir tmp-dir])
           (putenv "PLTCONFIGDIR" tmp-dir-s)
	   (putenv "PATH" (~a (find-console-bin-dir)
			      ":"
			      (getenv "PATH")))
           ;; Some tests use `git`; make sure those calls don't
           ;; operate on some other directory:
           (environment-variables-set! (current-environment-variables)
                                       #"GIT_DIR"
                                       #f)
           (t)))
      (λ ()
        (delete-directory/files tmp-dir))))
(define-syntax-rule (with-fake-installation e ...)
  (with-fake-installation* (λ ()  e ...)))

(define (with-fake-root* t)
  (define tmp-dir
    (make-temporary-file ".racket.fake-root~a" 'directory
                         (find-system-path 'home-dir)))
  (make-directory* tmp-dir)
  (dynamic-wind
      void
      (λ ()
         (define tmp-dir-s
           (path->string tmp-dir))
         (parameterize ([current-environment-variables
                         (environment-variables-copy
                          (current-environment-variables))])
           (putenv "PLTADDONDIR" tmp-dir-s)
           (t)))
      (λ ()
        (delete-directory/files tmp-dir))))
(define-syntax-rule (with-fake-root e ...)
  (with-fake-root* (λ ()  e ...)))

(define (with-thread start-thread thunk)
  (define thread-id (thread start-thread))
  (dynamic-wind
      void
      thunk
      (λ () (kill-thread thread-id))))

(require web-server/http
         web-server/servlet-env)
(define (start-file-server)
  (parameterize ([current-error-port (if (verbose?)
                                         (current-output-port)
                                         (open-output-nowhere))])
    (serve/servlet (λ (req) (response/xexpr "None"))
                   #:command-line? #t
                   #:port 9997
                   #:extra-files-paths (list (build-path test-directory "test-pkgs")))))

(require "basic-index.rkt")
(define *index-ht-1* (make-hash))
(define *index-ht-2* (make-hash))
(define (start-pkg-server index-ht port)
  (parameterize ([current-error-port (if (verbose?)
                                         (current-output-port)
                                         (open-output-nowhere))])
    (serve/servlet (pkg-index/basic
                    (λ (pkg-name)
                      (define r (hash-ref index-ht pkg-name #f))
                      (when (verbose?)
                        (printf "[>server ~a] ~a = ~a\n" port pkg-name r))
                      r)
                    (λ () index-ht))
                   #:command-line? #t
                   #:servlet-regexp #rx""
                   #:port port)))

(define servers-on? #f)
(define (with-servers* t)
  (cond
    [servers-on?
     (t)]
    [else
     (set! servers-on? #t)
     (with-thread
      (λ () (start-pkg-server *index-ht-1* 9990))
      (λ ()
        (with-thread
         (λ () (start-pkg-server *index-ht-2* 9991))
         (λ ()
           (with-thread
            (λ () (start-file-server))
            (λ ()
              (with-thread (λ () (serve-git-http-proxy! #:port 9996))
                           t)))))))]))
(define-syntax-rule (with-servers e ...)
  (with-servers* (λ () e ...)))

(define-syntax (pkg-tests stx)
  (syntax-case stx ()
    [(_ e ...)
     (with-syntax
         ([run-pkg-tests (datum->syntax #f 'run-pkg-tests)])
       (syntax/loc stx
         (begin
           (define (run-pkg-tests)
             (shelly-begin
              e ...))
           (provide run-pkg-tests)
           (module+ main
             (require racket/cmdline)
             (define verb? #t)
             (command-line
              #:once-each
              ["-q" "run quietly" (set! verb? #f)]
              #:args () (void))
             (parameterize ([verbose? verb?])
               (run-pkg-tests* run-pkg-tests))))))]))

(define (run-pkg-tests* t)
  (putenv "PLT_PKG_NOSETUP" "y")
  (with-servers
   (with-fake-installation*
    #:default-scope "user"
    (lambda ()
      (shelly-case "setup info cache" $ "raco setup -nDKxiI --no-foreign-libs")
      (with-fake-root
       (parameterize ([current-directory test-directory])
         (sync-test-directory)
         (t)))))))

(define-syntax-rule (shelly-install** message pkg rm-pkg (pre ...) (more ...))
  (with-fake-root
   (shelly-case
    (format "Test installation of ~a" message)
    pre ...
    $ "racket -e '(require pkg-test1)'" =exit> 1
    $ (format "raco pkg install --copy ~a" pkg)
    $ "racket -e '(require pkg-test1)'"
    more ...
    $ (format "raco pkg remove ~a" rm-pkg)
    $ "racket -e '(require pkg-test1)'" =exit> 1)))

(define-syntax-rule (shelly-install* message pkg rm-pkg more ...)
  (shelly-install** message pkg rm-pkg () (more ...)))

(define-syntax-rule (shelly-install message pkg more ...)
  (shelly-install* message pkg "pkg-test1" more ...))

(define (initialize-catalogs)
  (hash-set! *index-ht-1* "pkg-test1"
             (hasheq 'checksum
                     (file->string "test-pkgs/pkg-test1.zip.CHECKSUM")
                     'source
                     "http://localhost:9997/pkg-test1.zip"
                     'tags
                     '("first")))

  (hash-set! *index-ht-1* "pkg-test2"
             (hasheq 'checksum
                     (file->string "test-pkgs/pkg-test2.zip.CHECKSUM")
                     'source
                     "http://localhost:9997/pkg-test2.zip"
                     'dependencies
                     '("pkg-test1")))

  (hash-set! *index-ht-2* "pkg-test2-snd"
             (hasheq 'checksum
                     (file->string "test-pkgs/pkg-test2.zip.CHECKSUM")
                     'source
                     "http://localhost:9997/pkg-test2.zip"
                     'dependencies
                     '("pkg-test1")))

  (initialize-catalogs/git))

(define (initialize-catalogs/git)
  (define pkg-git.git (make-temporary-file "pkg-git-~a.git"))
  (delete-file pkg-git.git)
  (parameterize ([current-directory (build-path test-source-directory "test-pkgs")])
    (copy-directory/files (build-path test-source-directory "test-pkgs" "pkg-git") pkg-git.git))
  (define checksum
    (parameterize ([current-directory pkg-git.git])
      (system "git init")
      (system "git add -A")
      (system "git commit -m 'initial commit'")
      (string-trim
       (with-output-to-string
         (λ () (system "git rev-parse HEAD"))))))

  (match-define-values [_ pkg-git.git-filename _] (split-path pkg-git.git))
  (hash-set! *index-ht-1* "pkg-git"
             (hasheq 'checksum checksum
                     'source (~a "http://localhost:9996/" (path->string pkg-git.git-filename)))))

(define (set-file path content)
  (make-parent-directory* path)
  (call-with-output-file* 
   path
   #:exists 'truncate/replace
   (lambda (o) (displayln content o))))

(provide (all-defined-out))
