(ns auto-ap.jobs.sales-summaries-test
  (:require
   [auto-ap.datomic :refer [conn]]
   [auto-ap.integration.util :refer [setup-test-data wrap-setup]]
   [auto-ap.jobs.sales-summaries :as sut]
   [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- order
  "A sales order on `sales-date`, carrying whatever the case under test needs. The external id
  is Square-shaped by default because `get-service-charges` falls back to it when an order has
  no `:sales-order/vendor`."
  [client id attrs]
  (merge {:db/id (str "order-" id)
          :sales-order/external-id (str "square/order/TEST-" id)
          :sales-order/client client
          :sales-order/date sales-date
          :sales-order/total 100.0}
         attrs))

(defn- charge [id attrs]
  (merge {:db/id (str "charge-" id)
          :charge/external-id (str "square/charge/" id)
          :charge/type-name "CARD"
          :charge/total 100.0}
         attrs))

(defn- tip-for [client]
  (:ledger-mapped/amount (#'sut/get-tip client sales-date)))

(defn- service-charges-for [client]
  (#'sut/get-service-charges client sales-date))

(defn- enable-service-charges! [client]
  @(dc/transact conn [{:db/id client
                       :client/feature-flags [sut/service-charges-flag]}]))

(deftest tip-counts-a-reversal-on-an-untendered-order
  (testing "a return-only order has no tender to join through, so its negative tip must come
            from the order or the day credits a tip that was handed back"
    (let [{:strs [test-client-id]} (setup-test-data [])]
      @(dc/transact conn [(order test-client-id "return-only" {:sales-order/tip -12.0})])
      (is (= -12.0 (tip-for test-client-id))))))

(deftest tip-on-a-tendered-order-still-comes-from-the-tender
  (testing "the tender carries a tip the order does not — auto-gratuity booked as a service
            charge. Reading the order instead of the tender would drop it."
    (let [{:strs [test-client-id]} (setup-test-data [])]
      @(dc/transact conn [(order test-client-id "tendered"
                                 {:sales-order/tip 0.0
                                  :sales-order/charges [(charge "tendered" {:charge/tip 50.0})]})])
      (is (= 50.0 (tip-for test-client-id))))))

(deftest tip-on-an-ordinary-order-is-counted-once
  (testing "an order that agrees with its tender is not double counted by the additive form"
    (let [{:strs [test-client-id]} (setup-test-data [])]
      @(dc/transact conn [(order test-client-id "ordinary"
                                 {:sales-order/tip 5.0
                                  :sales-order/charges [(charge "ordinary" {:charge/tip 5.0})]})])
      (is (= 5.0 (tip-for test-client-id))))))

(deftest service-charges-need-the-feature-flag
  (testing "without the flag the summary behaves exactly as it does today"
    (let [{:strs [test-client-id]} (setup-test-data [])]
      @(dc/transact conn [(order test-client-id "square-sc"
                                 {:sales-order/vendor :vendor/ccp-square
                                  :sales-order/service-charge 50.0})])
      (is (nil? (service-charges-for test-client-id))))))

(deftest service-charges-credit-square-orders
  (testing "a service charge rides along in the tender, so it needs a credit to match"
    (let [{:strs [test-client-id]} (setup-test-data [])]
      (enable-service-charges! test-client-id)
      @(dc/transact conn [(order test-client-id "square-sc"
                                 {:sales-order/vendor :vendor/ccp-square
                                  :sales-order/service-charge 50.0})])
      (let [item (service-charges-for test-client-id)]
        (is (= 50.0 (:ledger-mapped/amount item)))
        (is (= :ledger-side/credit (:ledger-mapped/ledger-side item)))
        (is (= "Service Charges" (:sales-summary-item/category item)))))))

(deftest service-charges-count-both-signs
  (testing "a returned catering fee arrives as a negative service charge and is subtracted back
            out of returns, so dropping negatives loses the reversal"
    (let [{:strs [test-client-id]} (setup-test-data [])]
      (enable-service-charges! test-client-id)
      @(dc/transact conn [(order test-client-id "refunded-fee"
                                 {:sales-order/vendor :vendor/ccp-square
                                  :sales-order/service-charge -140.0})])
      (is (= -140.0 (:ledger-mapped/amount (service-charges-for test-client-id)))))))

(deftest service-charges-exclude-non-square-vendors
  (testing "ezCater service charges are commission deducted from the restaurant rather than
            collected from the diner, so crediting them would make the day worse"
    (let [{:strs [test-client-id]} (setup-test-data [])]
      (enable-service-charges! test-client-id)
      @(dc/transact conn [(order test-client-id "ezcater-sc"
                                 {:sales-order/external-id "ezcater/order/TEST-ezcater-sc"
                                  :sales-order/vendor :vendor/ccp-ezcater
                                  :sales-order/service-charge -75.0})])
      (is (nil? (service-charges-for test-client-id))))))

(deftest service-charges-recognise-square-orders-that-carry-no-vendor
  (testing "whole eras of Square orders have no :sales-order/vendor at all; a gate on vendor
            alone would silently credit nothing"
    (let [{:strs [test-client-id]} (setup-test-data [])]
      (enable-service-charges! test-client-id)
      @(dc/transact conn [(order test-client-id "vendorless" {:sales-order/service-charge 12.5})])
      (is (= 12.5 (:ledger-mapped/amount (service-charges-for test-client-id)))))))

(deftest service-charges-ignore-vendorless-orders-from-other-sources
  (testing "the external id fallback is Square-specific, not a catch-all for missing vendors"
    (let [{:strs [test-client-id]} (setup-test-data [])]
      (enable-service-charges! test-client-id)
      @(dc/transact conn [(order test-client-id "ezcater-vendorless"
                                 {:sales-order/external-id "ezcater/order/TEST-ezcater-vendorless"
                                  :sales-order/service-charge -75.0})])
      (is (nil? (service-charges-for test-client-id))))))

(deftest dirty-summaries-stop-at-the-client-boundary
  (testing "every dirty day for the client is returned, and none belonging to another client.

           :sales-summary/client+dirty sorts by client, so an unbounded index scan would walk
           every later client's summaries too — correct, but quadratic in the summary count."
    (let [{:strs [test-client-id]} (setup-test-data [])
          other (get-in @(dc/transact conn [{:db/id "other" :client/code (str "OTHER" (rand-int 100000))}])
                        [:tempids "other"])
          day (fn [client d dirty?]
                {:sales-summary/client client
                 :sales-summary/date d
                 :sales-summary/dirty dirty?})]
      @(dc/transact conn [(day test-client-id #inst "2026-08-01T07:00:00.000-00:00" true)
                          (day test-client-id #inst "2026-08-02T07:00:00.000-00:00" true)
                          (day test-client-id #inst "2026-08-03T07:00:00.000-00:00" false)
                          (day other #inst "2026-08-01T07:00:00.000-00:00" true)
                          (day other #inst "2026-08-02T07:00:00.000-00:00" true)])
      (let [mine (sut/dirty-sales-summaries test-client-id)]
        (is (= 2 (count mine)) "both dirty days, and not the clean one")
        (is (every? #(= test-client-id (:db/id (:sales-summary/client %))) mine)
            "and nothing belonging to the other client"))
      (is (= 2 (count (sut/dirty-sales-summaries other)))
          "the other client's own dirty days are still found"))))
