(ns debian.dh-clojure-lein-test
  (:require
   [clojure.string :as str]
   [clojure.test :refer [deftest is testing]]
   [debian.dh-clojure-lein.client :as client
    :refer [add-dep del-dep get-dep set-dep]]
   [debian.dh-clojure-lein.inject :as inject]
   [debian.dh-clojure-lein.util :as util :refer [dep-vecs->map map->dep-vecs]]
   [leiningen.core.project :as proj])
  (:import
   [clojure.lang ExceptionInfo]))

;; FIXME: add some plugins and managed-dependencies tests
;; FIXME: test validate-dep-vec or related add/set/del-dep behavior

(defmacro with-captured-abort
  "Executes the body, and if the body aborts, returns the abort message
  Throws an exception if there's no abort."
  [& body]
  `(let [k# (Object.)
         abort# (fn [& msgs#] (throw (ex-info (apply str msgs#) {:kind k#})))]
     (try
       (with-redefs [util/abort abort#]
         ~@body
         (throw (Exception. "Expected abort")))
       (catch ExceptionInfo ex#
         (if (identical? k# (-> ex# ex-data :kind))
           (ex-message ex#)
           (throw ex#))))))

(deftest profile-handling
  (let [proj
        ^{:active-profiles '(:base :system :user :provided :dev)}
        {:dependencies
         '([org.clojure/clojure "1.x"]
           [nrepl/nrepl "1.0.0" :exclusions ([org.clojure/clojure])])}]
    (is (= [:base :system :user :provided :dev]
           (client/active-profiles proj)))))

(def mini-deps
  ;; [[org.clojure/clojure "42.x"]]
  {{:group-id "org.clojure", :artifact-id "clojure"}
   {:artifact-id "clojure", :group-id "org.clojure", :version "42.x"}})

(deftest get-dep-behavior
  (is (not (get-dep mini-deps 'nope)))
  (is (= :nope (get-dep mini-deps 'nope :nope)))
  (is (= {:artifact-id "clojure", :group-id "org.clojure", :version "42.x"}
         (get-dep mini-deps 'org.clojure/clojure)))
  (is (= {:artifact-id "clojure", :group-id "org.clojure", :version "42.x"}
         (get-dep mini-deps 'org.clojure/clojure :nope))))

(deftest add-dep-behavior
  (is (= (merge mini-deps (dep-vecs->map '[[x "2.x"]]))
         (-> (add-dep mini-deps '[x "2.x"]))))
  (is (= (merge mini-deps (dep-vecs->map '[[x "2.x" :exclusions [y]]]))
         (-> (add-dep mini-deps '[x "2.x" :exclusions [y]]))))
  (is (= "add-dep applied to existing dependency org.clojure/clojure\n"
         (with-captured-abort
           (add-dep mini-deps '[org.clojure/clojure "42.x"]))))
  (is (= "add-dep applied to existing dependency org.clojure/clojure\n"
         (with-captured-abort
           (add-dep (assoc-in mini-deps
                              [{:group-id "org.clojure", :artifact-id "clojure"}
                               :exclusions]
                              (dep-vecs->map '[x]))
                    '[org.clojure/clojure "42.x"])))))

(deftest del-dep-behavior
  (is (= (merge mini-deps (dep-vecs->map '[[y "2.x"]]))
         (-> (merge mini-deps (dep-vecs->map '[[x "1.x"] [y "2.x"]]))
             (del-dep 'x))))
  (is (= (merge mini-deps (dep-vecs->map '[[y "2.x"]]))
         (-> (merge mini-deps (dep-vecs->map '[[x "1.x" :classifier "test"]
                                               [y "2.x"]]))
             (del-dep '[x :classifier "test"]))))
  (is (= (merge mini-deps (dep-vecs->map '[[y "2.x"]]))
         (-> (merge mini-deps (dep-vecs->map '[[x "1.x" :extension "jar"]
                                               [y "2.x"]]))
             (del-dep '[x :extension "jar"]))))
  (is (= "del-dep applied to missing dependency x\n"
         (with-captured-abort (del-dep mini-deps 'x))))
  (is (= "del-dep applied to missing dependency x\n"
         (with-captured-abort
           (-> (merge mini-deps (dep-vecs->map '[[x "1.x" :classifier "test"]]))
               (del-dep 'x)))))
  (is (= "del-dep applied to missing dependency x\n"
         (with-captured-abort
           (-> (merge mini-deps (dep-vecs->map '[[x "1.x" :extension "jar"]]))
               (del-dep 'x))))))

(deftest set-dep-behavior
  (is (= "set-dep applied to missing dependency x\n"
         (with-captured-abort (set-dep mini-deps 'x :version "1.x"))))

  (is (= (dep-vecs->map '[[x "3.x"]])
         (set-dep (dep-vecs->map '[[x "1.x"]]) 'x :version "3.x")))
  (is (= (dep-vecs->map '[[x "1.x"] [x "3.x" :classifier "test"]])
         (set-dep (dep-vecs->map '[[x "1.x"] [x "2.x" :classifier "test"]])
                  '[x :classifier "test"] :version "3.x")))
  (is (= (dep-vecs->map '[[x "1.x"] [x "3.x" :extension "jar"]])
         (set-dep (dep-vecs->map '[[x "1.x"] [x "2.x" :extension "jar"]])
                  '[x :extension "jar"] :version "3.x")))

  (is (= (dep-vecs->map '[[x "1.x" :exclusions [y]]])
         (set-dep (dep-vecs->map '[[x "1.x"]]) 'x :exclusions '[y])))
  (is (= (dep-vecs->map '[[x "1.x"]])
         (set-dep (dep-vecs->map '[[x "1.x" :exclusions [w y/z]]])
                  'x :exclusions nil)))

  (is (= (dep-vecs->map '[[x "1.x" :classifier "test" :exclusions [y]]])
         (set-dep (dep-vecs->map '[[x "1.x" :classifier "test"]])
                  '[x :classifier "test"] :exclusions '[y])))
  (is (= (dep-vecs->map '[[x "1.x" :classifier "test"]])
         (set-dep (dep-vecs->map '[[x "1.x" :classifier "test"
                                    :exclusions [w y/z]]])
                  '[x :classifier "test"] :exclusions nil)))

  (is (= (dep-vecs->map '[[x "1.x" :extension "jar" :exclusions [y]]])
         (set-dep (dep-vecs->map '[[x "1.x" :extension "jar"]])
                  '[x :extension "jar"] :exclusions '[y])))
  (is (= (dep-vecs->map '[[x "1.x" :extension "jar"]])
         (set-dep (dep-vecs->map '[[x "1.x" :extension "jar"
                                    :exclusions [w y/z]]])
                  '[x :extension "jar"] :exclusions nil))))

(defn lein-read-proj [form]
  (with-in-str (pr-str form)
    (proj/read-raw *in*)))

(deftest config-for-path-behavior
  (let [config-for-path #'inject/config-for-path]
    (let [config (config-for-path "x" #(eval '(defn foo [] 42)))]
      (is (= "x" (:path config)))
      (is (= clojure.lang.Namespace (-> config :ns class)))
      (is (identical? config (config-for-path "x")))
      (is (= 42 ((-> config :ns (ns-resolve 'foo))))))))

(deftest config-adjust-project-behavior
  (let [adjust #'inject/config-adjust-project
        config-for-path #'inject/config-for-path
        drop-config! #'inject/drop-config!
        proj (lein-read-proj '(defproject p "1.x" :dependencies [[x "2.x"]]))]
    (is (= "dh-clj-adjust-project was not defined in \"test-config\"\n"
           (with-captured-abort
             (adjust (config-for-path "test-config" #(do)) proj [:after-plugins]))))
    (drop-config! "test-config")
    (is (= '[[org.clojure/clojure "1.x"]]
           (let [proj (lein-read-proj
                       '(defproject p "1.x"
                          :dependencies [[org.clojure/clojure "1.12.0"]]))]
             (:dependencies (adjust nil proj [:after-plugins])))))
    (is (= '[[x/x "debian"]]
           (:dependencies (adjust nil proj [:after-plugins]))))

    (let [cfg '(defn dh-clj-adjust-project [proj when & _]
                 (case when
                   :after-plugins (assoc (proj) :version "new-version")
                   proj))
          cfg (config-for-path "test-config" #(eval cfg))]
      (is (= "new-version" (:version (adjust cfg proj [:after-plugins])))))
    (drop-config! "test-config")

    (let [cfg '(do
                 (require '[debian.dh-clojure-lein.client :as deb])
                 (defn dh-clj-adjust-project [proj when & _]
                   (case when
                     :after-plugins (update (proj) :dependencies deb/add-dep '[y "3.x"])
                     proj)))
          cfg (config-for-path "test-config" #(eval cfg))]
      (is (= '[[x/x "debian"] [y/y "3.x"]]
             (:dependencies (adjust cfg proj [:after-plugins])))))
    (drop-config! "test-config")

    (let [cfg '(do
                 (require '[debian.dh-clojure-lein.client :as deb])
                 (defn dh-clj-adjust-project [proj when & _]
                   (case when
                     :after-plugins (update (proj) :dependencies deb/del-dep 'x)
                     proj)))
          cfg (config-for-path "test-config" #(eval cfg))]
      (is (= [] (:dependencies (adjust cfg proj [:after-plugins])))))
    (drop-config! "test-config")

    (let [cfg '(do
                 (require '[debian.dh-clojure-lein.client :as deb])
                 (defn dh-clj-adjust-project [proj when & _]
                   (case when
                     :after-plugins (update (proj) :dependencies deb/set-dep 'x :version "yep")
                     proj)))
          cfg (config-for-path "test-config" #(eval cfg))]
      (is (= '[[x/x "yep"]]
             (:dependencies (adjust cfg proj [:after-plugins])))))
    (drop-config! "test-config")))
