(ns leiningen.debian.dh-clojure-lein-test
  (:require
   [clojure.string :as str]
   [clojure.test :refer [deftest is testing]]
   [leiningen.debian.dh-clojure-lein :as tgt])
  (:import
   [clojure.lang ExceptionInfo]
   [java.io PushbackReader]))

;; These tests assume that the config is fully vetted "at the edges"
;; i.e. on read, and so any tests not specifically testing config
;; validation may assume a correct config.

;; FIXME: add at least trivial plugins an managed-dependencies tests

(def mini-config
  '{:debian.dh-clojure-lein/version 0
    :project
    {some/thing
     {:edit
      {:version [= "1.x"]
       :dependencies [[= [org.clojure/clojure "42.x"]]]}}}})

(defn read-config-str [s]
  (with-in-str s (tgt/read-config (PushbackReader. *in*) "config")))

(defn cfg-w-deps [deps]
  (assoc-in mini-config [:project 'some/thing :edit :dependencies] deps))

(defmacro cfg-throws-msg [config rx]
  ;; Using a macro preserves the correct line number for failures
  `(is (~'thrown-with-msg? ExceptionInfo ~rx (read-config-str ~config))))

(deftest read-config-behavior
  (cfg-throws-msg "" #"empty config file \"config\"")
  (cfg-throws-msg "1 2" #"more than one form in \"config\"")
  (cfg-throws-msg "1" #"spec in \"config\" is not a map")
  (cfg-throws-msg "{}" #"missing .* debian.dh-clojure-lein/version")
  (cfg-throws-msg (-> mini-config (assoc :debian.dh-clojure-lein/version 42)
                      pr-str)
                  #"incorrect debian.dh-clojure-lein/version")
  (cfg-throws-msg (-> mini-config (assoc :exciting? true) pr-str)
                  #"unexpected top-level keys \(:exciting\?\)")
  (cfg-throws-msg (-> mini-config (assoc-in [:project :x/y] {}) pr-str)
                  #"project name :x/y is not a symbol")
  (cfg-throws-msg (-> mini-config (assoc-in [:project 'x] {:bad? true}) pr-str)
                  #"unexpected keys \(:bad\?\) in x project spec")
  (cfg-throws-msg (-> mini-config (assoc-in [:project 'x] {:edit true}) pr-str)
                  #"project x :edit is not a map")

  (cfg-throws-msg (-> mini-config (assoc-in [:project 'x] {:edit true}) pr-str)
                  #"project x :edit is not a map")

  (testing "top level, non-dependency :edit keys"
    (let [edited #(-> (assoc-in mini-config [:project 'some/thing :edit %1] %2)
                      pr-str)]
      (cfg-throws-msg (edited :version true)
                      #"invalid :edit true of :version")
      (cfg-throws-msg (edited :version '[+ "x" "y"])
                      #"invalid :edit \[\+ \"x\" \"y\"\] of :version")
      (cfg-throws-msg (edited :version '+) #"invalid :edit \+ of :version")
      (is (read-config-str (edited :version '-)))
      (is (read-config-str (edited :version '[+ "1.x"])))
      (is (read-config-str (edited :version '[= "1.x"])))))

  (testing "dependency edits"
    (cfg-throws-msg (-> "nope" cfg-w-deps pr-str) #"non-vector")
    (cfg-throws-msg (-> ["nope"] cfg-w-deps pr-str) #"non-vector")
    (cfg-throws-msg (-> [[]] cfg-w-deps pr-str) #"empty :dependencies :edit")
    (cfg-throws-msg (-> '[[- x/y z]] cfg-w-deps pr-str) #"more than one argument")
    (cfg-throws-msg (-> '[[+ x/y z]] cfg-w-deps pr-str) #"more than one argument")
    (cfg-throws-msg (-> '[[= x/y z]] cfg-w-deps pr-str) #"more than one argument")
    (cfg-throws-msg (-> '[[? x/y z]] cfg-w-deps pr-str) #"unrecognized :dependencies :edit")
    (is (read-config-str (-> '[[+ x/y]] cfg-w-deps pr-str)))))

(deftest non-dependency-project-edits
  (let [adjust tgt/adjust-project]
    (testing "-"
      (is (= {}
             (adjust {:version "1"} {:edit {:version '-}})))
      (is (thrown-with-msg?
           ExceptionInfo #"- applied to missing :version key"
           (adjust {} '{:edit {:version -}}))))
    (testing "+"
      (is (= {:version "1"}
             (adjust {} '{:edit {:version [+ "1"]}})))
      (is (thrown-with-msg?
           ExceptionInfo #"\+ applied to existing :version key"
           (adjust {:version "2"} {:edit {:version ['+ "1"]}}))))
    (testing "="
      (is (= {:version "2"}
             (adjust {:version "1"} {:edit {:version ['= "2"]}})))
      (is (thrown-with-msg?
           ExceptionInfo #"= applied to missing :version key"
           (adjust {} {:edit {:version ['= "2"]}}))))))

(deftest default-project-deps
  (let [adjust tgt/adjust-project]
    (is (= {:dependencies '[[x/x "debian"]]}
           (adjust {:dependencies '[[x]]} nil)))
    (is (= {:dependencies '[[x/x "debian"]]}
           (adjust {:dependencies '[[x "1"]]} nil)))
    (is (= {:dependencies '[[org.clojure/clojure "1.x"]]}
           (adjust {:dependencies '[[org.clojure/clojure]]} nil)))
    (is (= {:dependencies '[[org.clojure/clojure "1.x"]]}
           (adjust {:dependencies '[[org.clojure/clojure "1.11.2"]]} nil)))))

(deftest project-dep-
  (let [adjust tgt/adjust-project]
    (is (= {:dependencies '[[y/y "debian"]]}
           (adjust {:dependencies '[[x "1.x"] [y "2.x"]]}
                   {:edit {:dependencies '[[- x]]}})))
    (is (= {:dependencies '[[x/x "debian" :classifier "test"]]}
           (adjust {:dependencies '[[x "1.x"] [x "2.x" :classifier "test"]]}
                   {:edit {:dependencies '[[- x]]}})))
    (is (= {:dependencies '[[x/x "debian"]]}
           (adjust {:dependencies '[[x "1.x"] [x "2.x" :classifier "test"]]}
                   {:edit {:dependencies '[[- [x :classifier "test"]]]}})))
    (is (= {:dependencies '[[x/x "debian"]]}
           (adjust {:dependencies '[[x "1.x"] [x "2.x" :extension "test"]]}
                   {:edit {:dependencies '[[- [x :extension "test"]]]}})))
    (is (thrown-with-msg?
         ExceptionInfo #"- applied to missing dependency x"
         (adjust {:dependencies '[]}
                 {:edit {:dependencies '[[- x]]}})))))

(deftest project-dep+
  (let [adjust tgt/adjust-project]
    (is (= {:dependencies '[[x/x "debian"] [y/y "2.x"]]}
           (adjust {:dependencies '[[x "1.x"]]}
                   {:edit {:dependencies '[[+ [y "2.x"]]]}})))
    (is (= {:dependencies '[[x/x "debian"] [y/y "2.x" :exclusions [[z/z]]]]}
           (adjust {:dependencies '[[x "1.x"]]}
                   {:edit {:dependencies '[[+ [y "2.x" :exclusions [z]]]]}})))
    (is (thrown-with-msg?
         ExceptionInfo #"\+ applied to existing dependency x"
         (adjust {:dependencies '[[x]]}
                 {:edit {:dependencies '[[+ [x]]]}})))
    (is (thrown-with-msg?
         ExceptionInfo #"\+ applied to existing dependency x"
         (adjust {:dependencies '[[x :exclusions [y]]]}
                 {:edit {:dependencies '[[+ [x]]]}})))))

(deftest project-dep=
  (let [adjust tgt/adjust-project]
    (testing "missing"
      (is (thrown-with-msg?
           ExceptionInfo #"= applied to missing dependency \[x \"1\.x\"\]"
           (adjust {:dependencies []}
                   {:edit {:dependencies '[[= [x "1.x"]]]}}))))

    (testing "changes"
      (is (= {:dependencies '[[x/x "2.x"]]}
             (adjust {:dependencies '[[x "1.x"]]}
                     {:edit {:dependencies '[[= [x "2.x"]]]}})))
      (is (= {:dependencies '[[x/x "debian" :exclusions [[z/z]]]]}
             (adjust {:dependencies '[[x "1.x"]]}
                     {:edit {:dependencies '[[= [x :exclusions [z]]]]}})))

      (is (= {:dependencies '[[x/x "debian"]
                              [x/x "2.x" :classifier "test"]]}
             (adjust {:dependencies '[[x "1.x"]
                                      [x "1.x" :classifier "test"]]}
                     {:edit {:dependencies '[[= [x "2.x" :classifier "test"]]]}}))))

    (testing "deletions"
      (is (= {:dependencies '[[x/x "debian"]]}
             (adjust {:dependencies '[[x "1.x" :exclusions [z]]]}
                     {:edit {:dependencies '[[= [x :exclusions nil]]]}})))
      (is (thrown-with-msg?
           ExceptionInfo #"= dependency edit :classifier cannot be nil"
           (adjust {:dependencies '[[x "1.x" :classifier "z"]]}
                       {:edit {:dependencies '[[= [x :classifier nil]]]}})))
      (is (thrown-with-msg?
           ExceptionInfo #"= dependency edit :extension cannot be nil"
           (adjust {:dependencies '[[x "1.x" :extension "z"]]}
                       {:edit {:dependencies '[[= [x :extension nil]]]}}))))))
