(ns auto-ap.ssr.ledger.balance-sheet
  (:require
   [amazonica.aws.s3 :as s3]
   [auto-ap.datomic
             :refer [conn pull-many]]
   [auto-ap.graphql.utils :refer [assert-can-see-client]]
   [auto-ap.ledger :refer [build-account-lookup upsert-running-balance]]
   [auto-ap.ledger.reports :as l-reports]
   [auto-ap.logging :as alog]
   [auto-ap.pdf.ledger :refer [table->pdf]]
   [auto-ap.permissions :refer [wrap-must]]
   [auto-ap.routes.ledger :as route]
   [auto-ap.routes.utils
             :refer [wrap-client-redirect-unauthenticated]]
   [auto-ap.ssr-routes :as ssr-routes]
   [auto-ap.ssr.components :as com]
   [auto-ap.ssr.form-cursor :as fc]
   [auto-ap.ssr.hx :as hx]
   [auto-ap.ssr.ledger.report-table :as rtable]
   [auto-ap.ssr.svg :as svg]
   [auto-ap.ssr.ui :refer [base-page]]
   [auto-ap.ssr.utils
             :refer [apply-middleware-to-all-handlers clj-date-schema
                     html-response modal-response wrap-form-4xx-2
                     wrap-schema-enforce]]
   [auto-ap.time :as atime]
   [bidi.bidi :as bidi]
   [clj-pdf.core :as pdf]
   [clj-time.coerce :as coerce]
   [clj-time.core :as time]
   [clojure.java.io :as io]
   [clojure.string :as str]
   [config.core :refer [env] :as env]
   [datomic.api :as dc]
   [iol-ion.utils :refer [by]]
   [malli.core :as mc])
  (:import
   [java.util UUID]
   [org.apache.commons.io.output ByteArrayOutputStream]))





(def query-schema (mc/schema
                   [:maybe [:map
                                  [:client {:unspecified/value :all}
                                   [:or
                                    [:enum :all]
                                    [:vector {:coerce? true :min 1}
                                     [:entity-map {:pull [:db/id :client/name]}]]]]
                            [:include-deltas {:default false}
                                    [:boolean {:decode/string {:enter #(if (= % "on") true
                                  
                                                                           (boolean %))}}]]
                                  [:date {:unspecified/fn (fn [] [(atime/local-now)])}
                                   [:vector {:coerce? true
                                             :decode/string (fn [s] (if (string? s) (str/split s #", ")
                                                                        s))}
                                    clj-date-schema]] ]]))
;; TODO 
;; 1. Rerender form when running
;; 2. Don't throw crazy errors when missing a field
;; 3. General cleanup of the patterns  in run-balance-sheet
;; 4. Review ledger dialog

(defn get-report [{ {:keys [date client] :as qp} :query-params :as request}]
  (when (and date client)
    (let [client (if (= :all client) (take 5 (:clients request)) client)
          date (reverse (sort date ))
          client-ids (map :db/id client)
          _ (doseq [client-id client-ids]
              (assert-can-see-client (:identity request) client-id))
          
          _ (upsert-running-balance (into #{} client-ids))
          
          lookup-account (->> client-ids
                              (map (fn build-lookup [client-id]
                                     [client-id (build-account-lookup client-id)]))
                              (into {}))
          data (into []
                     (for [client-id client-ids
                           d date
                           [client-id account-id location debits credits balance count] (iol-ion.query/detailed-account-snapshot (dc/db conn) client-id (coerce/to-date d))
                           :let [account ((or (lookup-account client-id) {}) account-id)]]
                       {:client-id client-id
                        :account-id account-id
                        :location location
                        :debits debits
                        :credits credits
                        :count count
                        :amount balance
                        :account-type (:account_type account)
                        :numeric-code (:numeric_code account)
                        :name (:name account)
                        :period (coerce/to-date d)}))
          args          (assoc (:query-params request)
                               :periods (map coerce/to-date (filter identity date)))
          clients       (pull-many (dc/db conn) [:client/code :client/name :db/id] client-ids)
          
          pnl-data      (l-reports/->PNLData args data (by :db/id :client/code clients))
          report        (l-reports/summarize-balance-sheet pnl-data) ]
      (alog/info ::balance-sheet :params args)
      {:data report
       :report report})))

(defn maybe-trim-clients [request client ]
  (if (= :all client)
    (cond-> {:client (take 20 (:clients request))}
      (> (count (:clients request)) 20)
      (assoc :warning "You requested a report with more than 20 clients. This report will only contain the first 20."))
    {:client client}))

(defn balance-sheet* [{ {:keys [date client] } :query-params :as request}]
  [:div#report 
   (when (and date client)
     (let [{:keys [client warning]} (maybe-trim-clients request client)
           {:keys [data report]} (get-report (assoc-in request [:query-params :client] client))
           client-count (count (set (map :client-id (:data data)))) ]
       (list 
        [:div.text-2xl.font-bold.text-gray-600 (str "Balance Sheet - " (str/join ", " (map :client/name client))) ]
        (rtable/table {:widths      (cond-> (into [30 ] (repeat 13 client-count))
                               (> (count date) 1) (into (repeat 13 (* 2 client-count (dec (count date)))))
                               (and (> client-count 1) (= (count date) 1)) (conj 13))
                :investigate-url (bidi.bidi/path-for ssr-routes/only-routes ::route/investigate)
                :table       report
                :warning (not-empty (str/join "\n " (filter not-empty [warning (:warning report)])))} ))))])

(defn form* [request & children]
  (let [params (or (:query-params request) {})]
    (fc/start-form
      params
     (:form-errors request)
     [:div#balance-sheet-form.flex.flex-col.gap-4.mt-4
      [:div.flex.gap-8 
       [:form {:hx-get (bidi.bidi/path-for ssr-routes/only-routes ::route/run-balance-sheet) 
               :hx-target "#balance-sheet-form"
               :hx-swap "outerHTML"
               :hx-disabled-elt "find fieldset"}
        [:fieldset 
         [:div.flex.gap-8 {:x-data (hx/json {})}
          (fc/with-field :client
            (com/validated-inline-field 
             {:label "Customers" :errors (fc/field-errors)}
             (com/multi-typeahead {:name        (fc/field-name)
                                   :placeholder "Search for companies..."
                                   :class "w-64"
                                   :id          "client"
                                   :url (bidi/path-for ssr-routes/only-routes :company-search)
                                   :value       (fc/field-value)
                                   :value-fn :db/id
                                   :content-fn :client/name})))
          (fc/with-field :date
            (com/validated-inline-field {:label "Date"
                                         :errors (fc/field-errors)} 
                                        (com/dates-dropdown {:value (fc/field-value)
                                                             :name (fc/field-name)})))
          (fc/with-field :include-deltas
            (com/toggle {:name (fc/field-name)
                         :checked (fc/field-value)}
                        "Include Deltas"))
          (com/button {:color :primary :class "w-32"}
                      "Run")
          (com/button {:formaction (bidi.bidi/path-for ssr-routes/only-routes ::route/export-balance-sheet) } "Export PDF")]]] ]
      children])))

(defn form [request]
  (html-response (form* request
                        (balance-sheet* request))
                 :headers {"hx-retarget" "#balance-sheet-form"
                           "hx-push-url" (str "?" (:query-string request))}))

(defn balance-sheet [request]
  (base-page
   request
   (com/page {:nav              com/main-aside-nav
              
              :client-selection (:client-selection request)
              :clients          (:clients request)
              :client           (:client request)
              :identity         (:identity request)
              :request request}
             (apply com/breadcrumbs {} [[:a {:href (bidi/path-for ssr-routes/only-routes ::route/page)}
                                       "Ledger"]])
             (form* request))
   "Balance Sheet"))

(defn make-balance-sheet-pdf [request report]
  
  (let [output-stream (ByteArrayOutputStream.)
        client-count (count (or (seq (:client (:query-params request)))
                                (seq (:client (:form-params request)))))
        date (:date (:query-params request)) ]
    (pdf/pdf
      (-> [{:left-margin 10 :right-margin 10 :top-margin 15 :bottom-margin 15
            :size        :letter
            :font        {:size     6
                          :ttf-name "fonts/calibri-light.ttf"}}
           [:heading (str "Balance Sheet - " (str/join ", " (map :client/name (or (seq (:client (:query-params request)))
                                                                                  (seq (:client (:form-params request)))))))]]

          (conj [:paragraph {:color [128 0 0] :size 9} (:warning report)])
          (conj 
            (table->pdf report
                         (cond-> (into [30 ] (repeat client-count 13))
                                 (> (count date) 1) (into (repeat (* 2 client-count (dec (count date))) 13 ))
                                 (and (> client-count 1) (= (count date) 1)) (conj 13)))))
      output-stream)
    (.toByteArray output-stream)))

(defn join-names [client-ids]
  (str/replace (->> client-ids (pull-many (dc/db conn) [:client/name]) (map :client/name) (str/join "-")) #"[^\w]" "_" ))

(defn balance-sheet-args->name [request]
  (let [date (atime/unparse-local
                   (:date (:query-params request))
                   atime/iso-date)
        name    (->> request :query-params :client (map :db/id) join-names)]
    (format "Balance-sheet-%s-for-%s" date name)))

(defn print-balance-sheet [request]
  (let [uuid     (str (UUID/randomUUID))
        {:keys [client warning]} (maybe-trim-clients request (:client (:query-params request)))
        request  (assoc-in request [:query-params :client] client)
        pdf-data (make-balance-sheet-pdf request (:report (get-report request)))
        name     (balance-sheet-args->name request)
        key      (str "reports/balance-sheet/" uuid "/" name ".pdf")
        url      (str "https://" (:data-bucket env) "/" key)]
    (s3/put-object :bucket-name (:data-bucket env/env)
                   :key key
                   :input-stream (io/make-input-stream pdf-data {})
                   :metadata {:content-length (count pdf-data)
                              :content-type   "application/pdf"})
    @(dc/transact conn
                  [{:report/name    name
                    :report/client  (map :db/id client)
                    :report/key     key
                    :report/url     url
                    :report/creator (:user (:identity request))
                    :report/created (java.util.Date.)}])
    {:report/name name
     :report/url  url}))

;; TODO PRINT WARNING
(defn export [request]
  (modal-response
   (com/modal {}
              (com/modal-card
               {}
               "Ready!"
               (com/modal-body {}
                               (let [bs (print-balance-sheet request)]
                                 [:div.flex.flex-col.mt-4.space-y-4.items-center
                                  [:a {:href (:report/url bs)}
                                   [:div.w-24.h-24.bg-green-50.rounded-full.p-4.text-green-300 {:class " hover:scale-110 transition duration-100"}

                                    svg/download]]
                                  [:span.text-gray-800
                                   "Click "
                                   (com/link {:href (:report/url bs)} "here")
                                   " to download"]
                                  ]))
               nil))
   :headers (-> {}
                (assoc "hx-retarget" ".modal-stack")
                (assoc "hx-reswap" "beforeend"))))

(def key->handler
  (apply-middleware-to-all-handlers
   (->
    {::route/balance-sheet (-> balance-sheet
                                   (wrap-schema-enforce :query-schema query-schema)
                                   (wrap-form-4xx-2 balance-sheet))
     ::route/run-balance-sheet (-> form
                                   (wrap-schema-enforce :query-schema query-schema)
                                   (wrap-form-4xx-2 form))
     ::route/export-balance-sheet (-> export
                                   (wrap-schema-enforce :query-schema query-schema)
                                   (wrap-form-4xx-2 form))}
    )
   (fn [h]
     (-> h
         #_(wrap-merge-prior-hx)
         (wrap-must {:activity :read :subject :balance-sheet})
         (wrap-client-redirect-unauthenticated)))))
