(ns auto-ap.routes.checks
  (:require [auto-ap.db.companies :as companies]
            [auto-ap.db.vendors :as vendors]
            [auto-ap.db.invoices :as invoices]
            [auto-ap.utils :refer [by]]
            [auto-ap.numeric :refer [num->words]]
            [auto-ap.db.checks :as checks]
            [auto-ap.db.invoices-checks :as invoices-checks]
            [auto-ap.db.utils :refer [query]]
            [auto-ap.parse :as parse]
            [amazonica.aws.s3 :as s3]
            [hiccup.core :refer [html]]
            [auto-ap.routes.utils :refer [wrap-secure]]
            [auto-ap.time :refer [local-now]]
            [config.core :refer [env]]
            [compojure.core :refer [GET POST context defroutes
                                    wrap-routes]]
            [clojure.string :as str]
            [clj-pdf.core :as pdf]
            [clj-time.format :as f]
            [clj-time.core :as time]
            [clojure.java.io :as io])
  (:import [java.text DecimalFormat]
           [java.util UUID]
           [java.io ByteArrayOutputStream])) 

(def parser (f/formatter "MM/dd/YYYY"))
(defn date->str [t] 
  (f/unparse parser t))

(defn make-check-pdf [check]
  (let [output-stream (ByteArrayOutputStream.)]
    (pdf/pdf
     [{:left-margin 10 :right-margin 0 :top-margin 0 :bottom-margin 0}
      (let [{:keys [paid-to company check date amount memo] {vendor-name :name :as vendor} :vendor} check
            df (DecimalFormat. "#,###.00") 
            word-amount (num->words amount)
            
            amount (str "--" (.format df  amount) "--")]
            
        [:table {:num-cols 12 :border false :leading 11}
         [(let [{:keys [name bank] {:keys [street1 street2 city state zip ]} :address} company]
            [:cell {:colspan 4 } [:paragraph {:leading 14} name "\n" street1 "\n" (str city ", " state " " zip)] ])
          (let [{{:keys [name acct]} :bank} company]
            [:cell {:colspan 6 :align :center} [:paragraph {:style :bold} name] [:paragraph acct]])
          [:cell {:colspan 2 :size 13}
           check]]

         [[:cell {:colspan 9}]
          [:cell {:colspan 3} date]] 

         [[:cell {:size 16 :leading 16} "PAY"]
          [:cell {:size 6 :leading 6 }  "TO THE ORDER OF"]
          [:cell {:colspan 7} vendor-name]
          [:cell {:colspan 3} amount]]

         [[:cell {}]
          [:cell {:colspan 8} word-amount [:line]]
          [:cell {:colspan 3}]]

         [[:cell {:colspan 12} [:spacer]]]
         [[:cell {:colspan 12} [:spacer]]]
         [[:cell {:colspan 12} [:spacer]]]

         [[:cell "MEMO"]
          [:cell {:colspan 7} memo [:line]]
          [:cell {:colspan 4}]]

         [[:cell {:colspan 2}]
          [:cell {:colspan 10 :leading 30}
           [:phrase {:size 18 :ttf-name "public/micrenc.ttf"} (str "c" check "c a" (:acct-number (:bank company)) "a 10302c")]]]
         
         [[:cell {:colspan 12} [:spacer]]]
         [[:cell {:colspan 12} [:spacer]]]
         [[:cell {:colspan 12} [:spacer]]]
         [[:cell]
          (into
           [:cell {:colspan 9}]
           (let [{:keys [name]
                  {:keys [street1 city state zip bank]} :address} company]
             (list
              [:paragraph name]
              [:paragraph street1]
              [:paragraph city ", " state " " zip]
              )))
          [:cell {:colspan 2 :size 13}
           check]]

         [[:cell {:colspan 12} [:spacer]]]
         [[:cell {:colspan 12} [:spacer]]]
         [[:cell {:colspan 12} [:spacer]]]

         [[:cell]
          [:cell {:colspan 5} [:paragraph
                               vendor-name "\n"
                               (:street1 (:address vendor)) "\n"
                               (:city (:address vendor)) ", " (:state (:address vendor)) " " (:zip (:address vendor))]]
          [:cell {:align :right}
           "Paid to:\n"
           "Amount:\n"
           "Date:\n"]

          [:cell {:colspan 5}
           [:paragraph paid-to]
           [:paragraph amount]
           [:paragraph date]]]

         [[:cell {:colspan 3} "Memo:"]
          [:cell {:colspan 9} memo]]

         [[:cell {:colspan 12} [:spacer]]]
         [[:cell {:colspan 12} [:spacer]]]
         [[:cell {:colspan 12} [:spacer]]]
         [[:cell {:colspan 12} [:spacer]]]
         [[:cell {:colspan 12} [:spacer]]]
         [[:cell {:colspan 12} [:spacer]]]

         [[:cell {:colspan 5}] 
          [:cell {:align :right :colspan 2}
           "Check:\n"
           "Vendor:\n"
           "Bank Account:\n"
           "Paid To:\n"
           "Amount:\n"
           "Date:\n"]

          [:cell {:colspan 5}
           [:paragraph check]
           [:paragraph vendor-name]
           [:paragraph (:name (:bank company))]
           [:paragraph paid-to]
           [:paragraph amount]
           [:paragraph date]]]
         [[:cell {:colspan 3} "Memo:"]
          [:cell {:colspan 9} memo]]
         ])]
     output-stream)
    (.toByteArray output-stream)))

(defn make-pdfs [checks]
  (loop [[check & checks] checks]
    (when check
      (s3/put-object :bucket-name (:data-bucket env)
                     :key (:s3-key check)
                     :input-stream (-> check
                                       :pdf-data
                                       make-check-pdf
                                       (io/make-input-stream {}))
                     :metadata {:content-type "application/pdf"})
      (recur checks))))

(defn merge-pdfs [keys]
  (let [merged-pdf-stream (java.io.ByteArrayOutputStream.)
        uuid (str (UUID/randomUUID))]
    (apply pdf/collate (concat [merged-pdf-stream] (->> keys
                                                        (map #(s3/get-object (:data-bucket env) %))
                                                        (map :input-stream))))
    (s3/put-object :bucket-name (:data-bucket env)
                   :key (str "merged-checks/" uuid ".pdf")
                   :input-stream (io/make-input-stream (.toByteArray merged-pdf-stream) {})
                   :metadata {:content-length (count (.toByteArray merged-pdf-stream))
                              :content-type "application/pdf"})
    (str "http://" (:data-bucket env) ".s3-website-us-east-1.amazonaws.com/merged-checks/" uuid ".pdf")))

(defn check-for-invoices [invoices vendor-id vendors company bank-account-id index invoice-amounts]
  (let [uuid (str (UUID/randomUUID))
        vendor (vendors vendor-id)
        bank-account (first (filter #(= (:id %) bank-account-id) (:bank-accounts company)))

        memo (str "Invoice #'s: "
                  (str/join ", "
                            (map (fn [i]
                                   (str (:invoice-number i) "(" (invoice-amounts (:id i)) ")"))
                                 invoices)))]
    {:s3-uuid uuid
     :s3-key (str "checks/" uuid ".pdf") 
     :s3-url (str "http://" (:data-bucket env) ".s3-website-us-east-1.amazonaws.com/checks/" uuid ".pdf")
     :check-number (+ index (:check-number bank-account))
     :amount (reduce + 0 (map (comp invoice-amounts :id) invoices))
     :memo  memo
     :vendor-id (:id vendor)
     :company-id (:id company)
     :pdf-data {:vendor vendor
                :paid-to (:name vendor)
                :amount (reduce + 0 (map (comp invoice-amounts :id) invoices))
                :check (str (+ index (:check-number bank-account)))
                :memo  memo
                :date (date->str (local-now))
                :company {:name (:name company)
                          :address (:address company)
                          :bank {:name (:bank-name bank-account)
                                 :acct (:bank-code bank-account)
                                 :acct-number (:number bank-account)}}}
     :invoices (map :id invoices)}))


(defn print-checks [invoice-payments company-id bank-account-id]
  (let [invoices (invoices/get-multi (map :invoice-id invoice-payments))
        company (companies/get-by-id company-id)  
        vendors (by :id (vendors/get-all)) 
        invoice-amounts (by :invoice-id :amount invoice-payments)
        invoices-grouped-by-vendor (group-by :vendor-id invoices)
        checks (-> (for [[[vendor-id invoices] index] (map vector invoices-grouped-by-vendor (range))]
                     [invoices (checks/insert! (check-for-invoices invoices vendor-id vendors company bank-account-id index invoice-amounts))])
                   doall)
        invoice-checks (invoices-checks/insert-multi!
                        (mapcat
                         (fn [[invoices check]]
                           (map
                            (fn [i]
                              {:invoice-id (:id i)
                               :check-id (:id check)
                               :amount (invoice-amounts (:id i))})
                            invoices))
                         checks))
        updated-company (update company :bank-accounts
                                (fn [bas]
                                  (map (fn [ba]
                                         (if (= bank-account-id (:id ba))
                                           (update ba :check-number + (count checks))
                                           ba))
                                       bas)))]

    (make-pdfs (map second checks))
    (companies/upsert company-id updated-company)
    (doseq [{:keys [invoice-id amount]} invoice-payments]
      (invoices/apply-payment invoice-id amount))
    {:invoices (invoices/get-multi (map :id (doto (mapcat first checks) println)))
     :pdf-url (merge-pdfs (map (comp :s3-key second) checks))}))


(defroutes routes
  (wrap-routes
   (context "/checks" []
            (POST "/" {:keys [edn-params]}
                  (let [invoices (invoices/get-multi (:invoice-ids edn-params))
                        companies (into {}
                                        (map (fn [c] [(:id c) c])
                                             (companies/get-all))) 
                        vendors (into {}
                                      (map (fn [v] [(:id v) v])
                                           (vendors/get-all)))
                        invoices-grouped-by-vendor (group-by :vendor-id invoices)
                        checks (-> (for [[vendor-id invoices] invoices-grouped-by-vendor]
                                     [invoices (checks/insert! (check-for-invoices invoices vendor-id vendors companies))])
                                   doall)
                        invoice-checks (invoices-checks/insert-multi!
                                        (mapcat
                                         (fn [[invoices check]]
                                           (map
                                            (fn [i]
                                              {:invoice-id (:id i)
                                               :check-id (:id check)
                                               :amount (:total i)})
                                            invoices))
                                         checks)) ]

                    (make-pdfs (map second checks))
                    
                    {:status 200
                     :body (pr-str {:url (merge-pdfs (map (comp :s3-key second) checks))
                                    :checks checks
                                    :invoice-checks invoice-checks}) 
                     :headers {"Content-Type" "application/edn"}})))
   wrap-secure))
