(ns auto-ap.routes.auth
  (:require
   [auto-ap.datomic.users :as users]
   [buddy.sign.jwt :as jwt]
   [clj-http.client :as http]
   [clj-time.core :as time]
   [auto-ap.logging :as alog]
   [config.core :refer [env]]
   [com.brunobonacci.mulog :as mu]
   [clojure.java.io :as io]
   [clojure.edn :as edn]
   [auto-ap.session-version :as session-version]))

(def google-client-id "264081895820-0nndcfo3pbtqf30sro82vgq5r27h8736.apps.googleusercontent.com")
(def google-client-secret "OC-WemHurPXYpuIw5cT-B90g")

#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
(defn make-api-token []
  (jwt/sign {:user "API"
             :exp (time/plus (time/now) (time/days 1000))
             :user/role "admin"
             :user/name "API"}
            (:jwt-secret env)
            {:alg :hs512}))

(defn gzip [data]
  (let [data (pr-str data)
        raw (java.io.ByteArrayOutputStream.)]
    (with-open [output (-> raw
                           (io/output-stream)
                           (java.util.zip.GZIPOutputStream.))]
      (io/copy data output))
    (.encodeToString (java.util.Base64/getEncoder) (.toByteArray raw))))

(defn gunzip [b64]
  
  (let [raw-bytes (.decode (java.util.Base64/getDecoder) b64)
        raw       (java.io.ByteArrayInputStream. raw-bytes)
        out       (java.io.ByteArrayOutputStream.)]
    (with-open [compressed (-> raw
                           (io/input-stream)
                           (java.util.zip.GZIPInputStream.))]
      (io/copy compressed out))
    
    (edn/read-string (.toString out))))

(defn user->jwt [user oauth-token]
  (let [auth (cond-> {:user         (:user/name user)
                      :exp          (time/plus (time/now) (time/days 30))
                      :db/id (:db/id user)
                      :user/role    (name (:user/role user))
                      :user/name    (:user/name user)}
               (#{"admin" "read-only"} (name (:user/role user)))
               (assoc :gz-clients (->> (:user/clients user)
                                       (map (fn [c]
                                              (select-keys c [:client/code :db/id :client/locations])))

                                       gzip))
               (not (#{"read-only" "admin"} (name (:user/role user))))
               (assoc :user/clients
                      (->> (:user/clients user)
                           (map (fn [c]
                                  (select-keys c [:client/code :db/id :client/locations]))))))]
    (when (and user oauth-token)
      auth)))

(defn oauth [{{:strs [code state]} :query-params {:strs [host]} :headers :as request}]
  (try
    (let [auth    (-> "https://accounts.google.com/o/oauth2/token"
                      (http/post
                       {:form-params {"client_id"     google-client-id
                                      "client_secret" google-client-secret
                                      "code"          code
                                      "redirect_uri"  (str (:scheme env) "://" host  "/api/oauth")
                                      "grant_type"    "authorization_code"}
                        :as          :json})
                      :body)

          token   (:access_token auth)
          profile (-> (http/get "https://www.googleapis.com/oauth2/v1/userinfo"
                                {:headers {"Authorization" (str "Bearer " token)} :as :json})
                      :body)
          _ (mu/log ::got-profile
                    :profile profile)
          user    (users/find-or-insert! {:user/provider    "google"
                                          :user/provider-id (:id profile)
                                          :user/email (:email profile)
                                          :user/profile-image-url (:picture profile)
                                          :user/name        (:name profile)})]
      ;; TODO - these namespaces are not being transmitted/deserialized properly

      (if-let [jwt (user->jwt user token)]
        {:status  301
         :headers {"Location" (str (or (not-empty state) "/") "?jwt="
                                   (jwt/sign jwt
                                             (:jwt-secret env)
                                             {:alg :hs512}))}
         :session {:identity (dissoc jwt :exp)
                   :version session-version/current-session-version}}
        {:status 401
         :body   "Couldn't authenticate"}))
    (catch Exception e
      (alog/warn ::cant-authenticate
                 :error e)
      {:status 401
       :body   (str "Couldn't authenticate "  (.toString e))})))

(def routes {"api" {"/oauth" :oauth}})
(def match->handler {:oauth oauth})
