(ns ajet.chat.auth-gw.core
  "Auth Gateway — http-kit reverse proxy with authentication.

   Single edge entry point for all client traffic. Terminates sessions,
   validates tokens, and proxies authenticated requests to internal
   services (API, Web SM, TUI SM).

   System state held in a single atom for REPL-driven development."
  (:refer-clojure :exclude [reset!])
  (:require [clojure.tools.logging :as log]
            [org.httpkit.server :as http-kit]
            [ajet.chat.shared.config :as config]
            [ajet.chat.shared.db :as db]
            [ajet.chat.auth-gw.auth :as auth]
            [ajet.chat.auth-gw.routes :as routes]
            [ajet.chat.auth-gw.rate-limit :as rate-limit]
            [ajet.chat.auth-gw.setup :as setup])
  (:gen-class))

(defonce system (atom nil))

;;; ---------------------------------------------------------------------------
;;; Provider migration (env config → DB)
;;; ---------------------------------------------------------------------------

(defn- migrate-config-providers-to-db!
  "On first startup, if the DB has no OAuth providers but the config file
   has enabled providers, auto-migrate them to the DB for backward compat."
  [ds oauth-config]
  (when (zero? (auth/count-oauth-providers ds))
    (doseq [[provider-kw pcfg] oauth-config
            :when (:enabled pcfg)]
      (let [ptype (name provider-kw)]
        (log/info "Auto-migrating OAuth provider from config to DB:" ptype)
        (auth/insert-oauth-provider! ds
          (cond-> {:provider-type ptype
                   :display-name  (case provider-kw
                                    :github "GitHub"
                                    :gitea  "Gitea"
                                    :oidc   "SSO"
                                    ptype)
                   :slug          ptype
                   :client-id     (:client-id pcfg)
                   :client-secret (:client-secret pcfg)}
            (:base-url pcfg)   (assoc :base-url (:base-url pcfg))
            (:issuer-url pcfg) (assoc :issuer-url (:issuer-url pcfg))))))))

;;; ---------------------------------------------------------------------------
;;; Lifecycle
;;; ---------------------------------------------------------------------------

(defn start!
  "Start the Auth Gateway service.

   1. Load config (EDN + env vars)
   2. Create DB connection pool (HikariCP)
   3. Run migrations
   4. Initialize OAuth providers atom (from DB)
   5. Initialize setup-complete? atom
   6. Initialize rate limiter (in-memory atom)
   7. Start http-kit server with reitit router
   8. Log startup"
  [& [{:keys [config-overrides]}]]
  (when @system
    (log/warn "System already started — call (stop!) first")
    (throw (ex-info "System already running" {})))
  (let [config     (config/load-config {:resource "auth-gw-config.edn"})
        config     (if config-overrides
                     (merge config config-overrides)
                     config)
        _          (log/info "Loaded config:" (config/redact config))

        ;; Database — Auth GW has direct PG access for sessions/tokens
        ds         (db/make-datasource (:db config))
        _          (log/info "Database connection pool created")
        _          (when (get-in config [:db :migrations :enabled] true)
                     (db/migrate! ds (get-in config [:db :migrations])))

        ;; Auto-migrate OAuth providers from config to DB (backward compat)
        _          (migrate-config-providers-to-db! ds (:oauth config))

        ;; OAuth providers — loaded from DB, cached in atom
        oauth-provs (atom (auth/list-oauth-providers ds))
        _          (log/info "Loaded" (count @oauth-provs) "OAuth providers from DB")

        ;; Setup completion status — cached in atom
        setup-atom (atom nil)

        ;; Rate limiter
        limiter    (rate-limit/make-limiter)
        cleanup-fn (rate-limit/start-cleanup-task! limiter)
        _          (log/info "Rate limiter initialized")

        ;; System map
        sys        {:config              config
                    :ds                  ds
                    :limiter             limiter
                    :oauth-providers-atom oauth-provs
                    :setup-complete-atom  setup-atom}

        ;; HTTP server
        handler    (routes/app sys)
        port       (get-in config [:server :port] 3000)
        host       (get-in config [:server :host] "0.0.0.0")
        server     (http-kit/run-server handler
                     {:port     port
                      :ip       host
                      :max-body (* 12 1024 1024)
                      ;; Don't buffer SSE responses
                      :server-header "ajet-auth-gw"})]
    (clojure.core/reset! system (assoc sys
                     :server     server
                     :port       port
                     :cleanup-fn cleanup-fn))
    (log/info (str "Auth Gateway started on " host ":" port))
    @system))

(defn stop!
  "Stop the Auth Gateway. Shuts down HTTP server, DB pool in order."
  []
  (when-let [sys @system]
    (log/info "Shutting down Auth Gateway...")

    ;; Stop HTTP server (wait up to 30s for in-flight requests)
    (when-let [server (:server sys)]
      (server :timeout 30000)
      (log/info "HTTP server stopped"))

    ;; Cancel rate limiter cleanup task
    (when-let [cleanup-fn (:cleanup-fn sys)]
      (future-cancel cleanup-fn)
      (log/info "Rate limiter cleanup task stopped"))

    ;; Close DB pool
    (when-let [ds (:ds sys)]
      (try
        (db/close-datasource ds)
        (log/info "Database connection pool closed")
        (catch Exception e
          (log/error e "Error closing database pool"))))

    (clojure.core/reset! system nil)
    (log/info "Auth Gateway stopped")))

(defn reset!
  "Stop then start the system (REPL convenience)."
  []
  (stop!)
  (start!))

;;; ---------------------------------------------------------------------------
;;; Entry point
;;; ---------------------------------------------------------------------------

(defn -main [& _args]
  (start!)

  ;; Graceful shutdown hook
  (.addShutdownHook
    (Runtime/getRuntime)
    (Thread. ^Runnable (fn []
                         (log/info "Shutdown hook triggered")
                         (stop!))))

  ;; Block main thread
  @(promise))
