(ns auto-ap.handler
  (:require
   [amazonica.core :refer [defcredential]]
   [auto-ap.client-routes :as client-routes]
   [auto-ap.ssr-routes :as ssr-routes]
   [auto-ap.logging :as alog]
   [auto-ap.routes.auth :as auth]
   [auto-ap.routes.exports :as exports]
   [auto-ap.routes.ezcater :as ezcater]
   [auto-ap.routes.graphql :as graphql]
   [auto-ap.routes.health :as health]
   [auto-ap.routes.invoices :as invoices]
   [auto-ap.routes.queries :as queries]
   [auto-ap.routes.yodlee2 :as yodlee2]
   [auto-ap.ssr.core :as ssr]
   [bidi.bidi :as bidi]
   [bidi.ring :refer [->ResourcesMaybe make-handler]]
   [buddy.auth.backends.session :refer [session-backend]]
   [buddy.auth.backends.token :refer [jws-backend]]
   [buddy.auth.middleware :refer [wrap-authentication wrap-authorization]]
   [clojure.string :as str]
   [com.brunobonacci.mulog :as mu]
   [config.core :refer [env]]
   [ring.middleware.edn :refer [wrap-edn-params]]
   [ring.middleware.multipart-params :as mp]
   [ring.middleware.params :refer [wrap-params]]
   [ring.middleware.reload :refer [wrap-reload]]
   [ring.middleware.session :refer [wrap-session]]
   [ring.middleware.session.cookie :refer [cookie-store]]
   [ring.util.response :as response]
   [unilog.context :as lc]
   [clj-time.coerce :as coerce]
   [clj-time.core :as time]))

(when (:aws-access-key-id env)
  (defcredential (:aws-access-key-id env) (:aws-secret-access-key env) (:aws-region env)))

(defn deep-merge [v & vs]
  (letfn [(rec-merge [v1 v2]
            (if (and (map? v1) (map? v2))
              (merge-with deep-merge v1 v2)
              v2))]
    (when (some identity vs)
      (reduce #(rec-merge %1 %2) v vs))))

(def all-routes ["/" (-> (into []
                               (deep-merge ssr-routes/routes
                                           (second client-routes/routes)
                                           graphql/routes
                                           ezcater/routes
                                           health/routes
                                           queries/routes2
                                           yodlee2/routes
                                           auth/routes
                                           invoices/routes
                                           exports/routes2))
                         (conj ["" (->ResourcesMaybe {:prefix "public/"})])
                         (conj [true :not-found])) ;; always go for not found as last resort, have to switch to vec in order for that to work
                 ])

(defn not-found [_]
  {:status  404
   :headers {}
   :body    ""})

(defn render-index [_]
  (response/resource-response "index.html" {:root "public"}))

(def match->handler-lookup 
  (-> {:not-found not-found}
      (merge ssr/key->handler)
      (merge graphql/match->handler)
      (merge ezcater/match->handler)
      (merge health/match->handler)
      (merge queries/match->handler)
      (merge yodlee2/match->handler)
      (merge auth/match->handler)
      (merge invoices/match->handler)
      (merge exports/match->handler)
      (merge
        (into {}
              (map

                (fn [k]
                  [k  render-index])
                client-routes/all-matches)))))

(def match->handler
  (fn [route]
    (or (get match->handler-lookup route)
        route)))


(def route-handler
  (make-handler all-routes
                match->handler))

(defn wrap-guess-route [handler]
  (fn [{:keys [uri request-method] :as request} ]
    (let [matched-route (:handler
                         (bidi.bidi/match-route all-routes
                                                uri
                                                :request-method request-method))]
      (handler (assoc request
                      :matched-route
                      matched-route)))))

(defn test-match-route [method uri]
  (bidi.bidi/match-route all-routes
                         uri
                         :request-method method))


(def auth-backend (jws-backend {:secret (:jwt-secret env) :options {:alg :hs512}}))

(defn wrap-logging [handler]
  (fn [request]
    (mu/with-context {:uri       (:uri request)
                      :query (:uri request)
                      :request-method (:request-method request)
                      :user (:identity request)
                      :user-role (:user/role (:identity request))
                      :user-name (:user/name (:identity request))}
      (mu/trace ::http-request-trace
        []
        (lc/with-context {:uri       (:uri request)
                          :source    "request"
                          :user-role (:user/role (:identity request))
                          :user-name (:user/name (:identity request))}
          

          (when-not (str/includes? (:uri request) "health-check")
            (alog/info ::http-request-starting))
          (try 
            (let [response (handler request)]
              (alog/info ::http-request-done
                         :status-code (:status response))
              response)
            (catch Exception e
              (alog/error ::request-error
                          :exception e)
              (throw e))))))))

(defn wrap-idle-session-timeout
  [handler ]
  (fn [request]
    (let [session  (:session request {})
          end-time (coerce/to-date-time (::idle-timeout session))]
      (if (and end-time (time/before? end-time (time/now)))
        {:session nil
         :status  302
         :headers {"Location" "/login"}}
        (when-let [response (handler request)]
          (let [session (:session response session)]
            (if (nil? session)
              response
              (let [end-time (time/plus (time/now) (time/days 2))]
                (assoc response :session (assoc session ::idle-timeout (coerce/to-date end-time)))))))))))

#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
(def app
  (->  route-handler
       (wrap-guess-route)
       (wrap-authorization auth-backend
                           )
       (wrap-authentication auth-backend
                            (session-backend {:authfn (fn [auth]
                                                        (dissoc auth :exp))}))

       (wrap-idle-session-timeout)
       (wrap-session {:store (cookie-store
                               {:key
                                (byte-array
                                  [42, 52, -31, 105, -126, -33, -118, -69, -82, -59, -15, -69, -38, 103, -102, -1])} )})
       (wrap-reload)
       (wrap-params)
       (mp/wrap-multipart-params)
       (wrap-edn-params)
       (wrap-logging)))

