;; Copyright 2024 Ben Sturmfels
;; License: GPLv3-or-later

(ns core
  "Beancount importer for Paychex Pay Item Details report."
  (:require [clojure.set :as set]
            [clojure.string :as str]
            [clojure.tools.cli :refer [parse-opts]]
            [import :as import])
  (:gen-class))

(def cli-options
  [[nil "--csv FILE" "Pay Item Details CSV report"]
   [nil "--date DATE" "Date used for the transactions (YYYY-MM-DD)"
    :validate [#(re-matches #"\d{4}-\d{2}-\d{2}" %) "Must be of format YYYY-MM-DD"]
    :default "TODO-DATE"]
   [nil "--period PERIOD" "Month/year covered by the pay run eg. \"December 2023\""
    :default "TODO-PERIOD"]
   [nil "--total-fees NUM" "Total fee charged by Paychex, eg. \"206.50\""
    :parse-fn bigdec
    :default 0M]
   [nil "--pay-receipt-no REFERENCE" "Payroll receipt number, eg. \"rt:111/222\""
    :default "TODO-PAY-RECEIPT"]
   [nil "--pay-invoice-no REFERENCE" "Payroll invoice number, eg. \"rt:111/222\""
    :default "TODO-PAY-INVOICE"]
   [nil "--fees-receipt-no REFERENCE" "Paychex fees receipt number, eg. \"rt:111/222\""
    :default "TODO-FEES-RECEIPT"]
   [nil "--fees-invoice-no REFERENCE" "Paychex fees invoice number, eg. \"rt:111/222\""
    :default "TODO-FEES-INVOICE"]
   [nil "--retirement-receipt-no REFERENCE" "Retirement receipt number, eg. \"rt:111/222\""
    :default "TODO-RETIREMENT-RECEIPT"]
   [nil "--retirement-invoice-no REFERENCE" "Retirement receipt number, eg. \"rt:111/222\""
    :default "TODO-RETIREMENT-INVOICE"]
   [nil "--project EMPLOYEE:PROJECT" "Allocate an employee to a specific project, eg. \"Doe-Jane:Outreachy\". Use once for each employee."
    :multi true
    :validate [#(= 2 (count %)) "Must be of the form \"name:project\""]
    :parse-fn #(str/split % #":")
    :default {}
    :assoc-fn (fn [m k [name proj]] (assoc-in m [k name] proj))]
   ["-h" "--help"]])

(defn unmatched-employees
  "Identify any mismatches between employees in the pay run and --project employee allocations."
   [records projects]
  (set/difference (set (keys projects)) (->> records (map :name) set)))

(defn -main [& args]
  (let [{:keys [options errors summary]} (parse-opts args cli-options)
        {:keys [date period pay-receipt-no pay-invoice-no total-fees fees-receipt-no fees-invoice-no retirement-receipt-no retirement-invoice-no project]} options]
    (when (:help options)
      (println summary)
      (System/exit 0))
    (when errors
      (println
       (str "The following errors occurred while parsing your command:\n\n"
            (str/join \newline errors)))
      (System/exit 1))
    (let [records (import/read-csv (:csv options))
          imported (concat (import/net-pay date period pay-invoice-no project records)
                           (import/individual-taxes date period pay-invoice-no retirement-invoice-no project records)
                           (import/employer-taxes date period pay-invoice-no project records)
                           (import/net-pay-ach-debit date period pay-receipt-no pay-invoice-no {} records)
                           (import/taxes-ach-debit date period pay-receipt-no pay-invoice-no {} records)
                           (import/fees date period fees-receipt-no fees-invoice-no total-fees project records)
                           (import/retirement date period retirement-receipt-no retirement-invoice-no records))
          unmatched (unmatched-employees records project)]
      (when-not (empty? unmatched)
        (println
         (str "Could not find these employees in the payroll:\n\n"
              (str/join ", " unmatched)))
        (System/exit 1))
      (doseq [i imported]
        (println (import/render-transaction i))))))

(comment
  ;; Examples to exercise the importer during development.
  (require
   '[parse :refer [parse sort-postings]]
   '[lambdaisland.deep-diff2 :as dd])

  ;; These examples are not included with the code for privacy reasons.
  (require '[examples])

  (def records (import/read-csv "/home/ben/Downloads/2024-01-29_Pay-Item-Details_2024-01.csv"))
  (def imported
    (concat (import/net-pay "2024-01-31" "January 2024" "rt:19462/685751" {} records)
            (import/individual-taxes "2024-01-31" "January 2024" "rt:19462/685751" "rt:19403/685602" {} records)
            (import/employer-taxes "2024-01-31" "January 2024" "rt:19462/685751" {} records)
            (import/net-pay-ach-debit "2024-01-31" "January 2024" "TODO-PAY-RECEIPT" "TODO-PAY-INVOICE" {} records)
            (import/taxes-ach-debit "2024-01-31" "January 2024" "TODO-PAY-RECEIPT" "TODO-PAY-INVOICE" {} records)
            (import/fees "2024-01-31" "January 2024" "rt:19459/675387" "rt:19459/674887" 206.50M {} records)
            (import/retirement "2024-01-31" "January 2024" "rt:19403/685929" "rt:19403/685602" records)))
  ;; Compare hand-written transactions with imported (ignoring ordering).
  (dd/pretty-print
   (dd/diff
    (sort-postings (parse examples/jan-2024-with-liabilities))
    (sort-postings imported)))

  ;; Print out text transactions
  (doseq [i imported]
    (println (import/render-transaction i)))

  )