(ns auto-ap.jobs.rekey-square-external-ids-test
  (:require
   [auto-ap.datomic :refer [conn]]
   [auto-ap.integration.util :refer [setup-test-data wrap-setup]]
   [auto-ap.jobs.rekey-square-external-ids :as sut]
   [clojure.string]
   [clojure.test :refer [deftest is testing use-fixtures]]
   [datomic.api :as dc]))

(use-fixtures :each wrap-setup)

(def sales-date #inst "2026-08-01T07:00:00.000-00:00")

(defn- charge-count []
  (count (dc/q '[:find ?e :where [?e :charge/external-id]] (dc/db conn))))

(defn- charges-of [order]
  (->> (dc/datoms (dc/db conn) :eavt order :sales-order/charges)
       (map :v)
       (map (fn [c] {:eid c
                     :key (:v (first (dc/datoms (dc/db conn) :eavt c :charge/external-id)))
                     :total (:v (first (dc/datoms (dc/db conn) :eavt c :charge/total)))
                     :tip (:v (first (dc/datoms (dc/db conn) :eavt c :charge/tip)))}))
       vec))

(defn- parents-of [charge]
  (reduce (fn [n _] (inc n)) 0 (dc/datoms (dc/db conn) :vaet charge :sales-order/charges)))

(defn- two-orders-sharing-one-charge []
  (let [{:strs [test-client-id]} (setup-test-data [])
        other (get-in @(dc/transact conn [{:db/id "other" :client/code "NGCC"}]) [:tempids "other"])
        tx @(dc/transact conn [{:db/id "charge"
                                :charge/external-id "square/charge/shared1"
                                :charge/type-name "CARD"
                                :charge/total 120.0
                                :charge/tip 20.0}
                               {:db/id "order-a"
                                :sales-order/external-id "square/order/NGCD-CD-o1"
                                :sales-order/client test-client-id
                                :sales-order/location "CD"
                                :sales-order/date sales-date
                                :sales-order/charges ["charge"]}
                               {:db/id "order-b"
                                :sales-order/external-id "square/order/NGCC-CC-o1"
                                :sales-order/client other
                                :sales-order/location "CC"
                                :sales-order/date sales-date
                                :sales-order/charges ["charge"]}])]
    {:order-a (get-in tx [:tempids "order-a"])
     :order-b (get-in tx [:tempids "order-b"])
     :charge (get-in tx [:tempids "charge"])
     :code-a (:client/code (dc/entity (dc/db conn) test-client-id))
     :code-b "NGCC"}))

(deftest a-shared-charge-starts-with-two-parents
  (testing "the condition under test really exists before the split runs"
    (let [{:keys [charge]} (two-orders-sharing-one-charge)]
      (is (= 1 (charge-count)))
      (is (= 2 (parents-of charge))
          "one charge entity, referenced by both clients' orders"))))

(deftest split-gives-each-order-its-own-charge
  (testing "each order ends up with its own charge, scoped to its own client, carrying the same
            amounts — so the component relationship means what it says and retracting one order
            cannot delete the other's payment"
    (let [{:keys [order-a order-b code-a code-b]} (two-orders-sharing-one-charge)
          result (sut/split-and-rekey-charges! [order-a order-b] 100)]
      (is (= {:rekeyed 1 :cloned 1} result) "first order keeps it, second gets a copy")
      (is (= 2 (charge-count)) "exactly one new entity was created")

      (let [a (charges-of order-a)
            b (charges-of order-b)]
        (is (= 1 (count a)))
        (is (= 1 (count b)))
        (is (= (str "square/charge/" code-a "-CD-shared1") (:key (first a))))
        (is (= (str "square/charge/" code-b "-CC-shared1") (:key (first b))))
        (is (not= (:eid (first a)) (:eid (first b))) "two distinct entities")
        (is (= 120.0 (:total (first a)) (:total (first b))) "amounts copied")
        (is (= 20.0 (:tip (first a)) (:tip (first b))) "tips copied")
        (is (= 1 (parents-of (:eid (first a)))))
        (is (= 1 (parents-of (:eid (first b))))
            "no charge has more than one parent order any more")))))

(deftest split-leaves-an-unshared-charge-alone
  (testing "an order that already owns its charge outright is only re-keyed, never cloned"
    (let [{:strs [test-client-id]} (setup-test-data [])
          tx @(dc/transact conn [{:db/id "charge"
                                  :charge/external-id "square/charge/solo1"
                                  :charge/total 50.0}
                                 {:db/id "order"
                                  :sales-order/external-id "square/order/NGCD-CD-o2"
                                  :sales-order/client test-client-id
                                  :sales-order/location "CD"
                                  :sales-order/date sales-date
                                  :sales-order/charges ["charge"]}])
          order (get-in tx [:tempids "order"])
          code (:client/code (dc/entity (dc/db conn) test-client-id))]
      (is (= {:rekeyed 1 :cloned 0} (sut/split-and-rekey-charges! [order] 100)))
      (is (= 1 (charge-count)) "nothing was created")
      (is (= (str "square/charge/" code "-CD-solo1") (:key (first (charges-of order))))))))

(deftest split-is-idempotent
  (testing "re-running over an already-split database changes nothing further"
    (let [{:keys [order-a order-b]} (two-orders-sharing-one-charge)]
      (sut/split-and-rekey-charges! [order-a order-b] 100)
      (let [after-first (charge-count)]
        (is (= {:rekeyed 0 :cloned 0} (sut/split-and-rekey-charges! [order-a order-b] 100))
            "every charge already carries the key its order expects, so there is nothing to do")
        (is (= after-first (charge-count)) "and no further entities appear")))))

(deftest clone-is-not-double-scoped-across-batches
  (testing "with a batch size of one, the second order sees a charge already carrying the first
            client's scope. It must clone using the underlying Square id, not re-scope the scoped
            key — otherwise the entity ends up keyed NGCD-CD-NGCC-CC-<id>, the importer computes
            the correct key, misses, and creates a second charge that doubles the tender."
    (let [{:keys [order-a order-b code-a code-b]} (two-orders-sharing-one-charge)]
      (sut/split-and-rekey-charges! [order-a order-b] 1)
      (let [ka (:key (first (charges-of order-a)))
            kb (:key (first (charges-of order-b)))]
        (is (= (str "square/charge/" code-a "-CD-shared1") ka))
        (is (= (str "square/charge/" code-b "-CC-shared1") kb))
        (is (not (clojure.string/includes? kb (str code-a "-CD")))
            "the clone carries one scope, not two")
        (is (= 2 (charge-count)))))))

(deftest two-orders-of-the-same-client-keep-sharing
  (testing "one payment covering two of the SAME client's orders is left shared, on purpose.

           There is no second name to give a copy — both orders compute the same one — and a copy
           would double that client's takings for the day. The component cascade still reaches
           these, which is why remove-voided-orders needs its own guard."
    (let [{:strs [test-client-id]} (setup-test-data [])
          tx @(dc/transact conn [{:db/id "charge"
                                  :charge/external-id "square/charge/same1"
                                  :charge/total 75.0}
                                 {:db/id "o1" :sales-order/external-id "square/order/x-1"
                                  :sales-order/client test-client-id :sales-order/location "CD"
                                  :sales-order/date sales-date :sales-order/charges ["charge"]}
                                 {:db/id "o2" :sales-order/external-id "square/order/x-2"
                                  :sales-order/client test-client-id :sales-order/location "CD"
                                  :sales-order/date sales-date :sales-order/charges ["charge"]}])
          o1 (get-in tx [:tempids "o1"]) o2 (get-in tx [:tempids "o2"])
          code (:client/code (dc/entity (dc/db conn) test-client-id))]
      (is (= {:rekeyed 1 :cloned 0} (sut/split-and-rekey-charges! [o1 o2] 100))
          "renamed once, not copied")
      (is (= 1 (charge-count)) "no copy was made, so the takings are not doubled")
      (is (= (str "square/charge/" code "-CD-same1") (:key (first (charges-of o1)))))
      (is (= (:eid (first (charges-of o1))) (:eid (first (charges-of o2))))
          "both orders still point at the one payment"))))
