(ns ajet.chat.auth-gw.routes
  "Reitit router for the Auth Gateway.

   Routes are divided into:
   - Self-handled: auth pages, health check, invite landing, setup wizard
   - Proxied: all other routes forwarded to internal services
     with session/token validation and header injection"
  (:require [clojure.string :as str]
            [clojure.tools.logging :as log]
            [reitit.ring :as ring]
            [ring.middleware.cookies :refer [wrap-cookies]]
            [ring.middleware.params :refer [wrap-params]]
            [ajet.chat.auth-gw.auth :as auth]
            [ajet.chat.auth-gw.oauth :as oauth]
            [ajet.chat.auth-gw.proxy :as proxy]
            [ajet.chat.auth-gw.pages :as pages]
            [ajet.chat.auth-gw.middleware :as mw]
            [ajet.chat.auth-gw.rate-limit :as rl]
            [ajet.chat.auth-gw.setup :as setup]))

;;; ---------------------------------------------------------------------------
;;; Auth helpers
;;; ---------------------------------------------------------------------------

(defn- extract-remote-addr
  "Get the client's IP address from the request."
  [request]
  (or (get-in request [:headers "x-forwarded-for"])
      (:remote-addr request)
      "unknown"))

(defn- session-auth
  "Validate session cookie. Returns auth-info map or nil."
  [ds request cookie-name]
  (let [token (auth/extract-session-cookie request cookie-name)]
    (auth/validate-session ds token)))

(defn- api-token-auth
  "Validate Bearer token from Authorization header. Returns auth-info map or nil."
  [ds request]
  (let [auth-header (get-in request [:headers "authorization"])]
    (auth/validate-api-token ds auth-header)))

(defn- webhook-auth
  "Validate webhook Bearer token. Returns auth-info map or nil."
  [ds request]
  (let [auth-header (get-in request [:headers "authorization"])]
    (auth/validate-webhook-token ds auth-header)))

(defn- web-redirect-to-login
  "Redirect to login page (for web browser requests that fail auth)."
  []
  {:status  302
   :headers {"Location" "/auth/login"}
   :body    ""})

(defn- json-401
  "Return a 401 JSON response (for API/TUI requests that fail auth)."
  [trace-id]
  {:status  401
   :headers {"Content-Type" "application/json"
             "X-Trace-Id"   (or trace-id "")}
   :body    "{\"error\":\"unauthorized\",\"message\":\"Invalid or missing authentication\"}"})

(defn- is-web-route?
  "Check if the URI is a web-browser route (should redirect on auth failure)."
  [uri]
  (or (= uri "/")
      (= uri "/app")
      (= uri "/setup")
      (str/starts-with? uri "/app/")
      (str/starts-with? uri "/sse/")
      (str/starts-with? uri "/web/")))

;;; ---------------------------------------------------------------------------
;;; Proxy handlers with auth
;;; ---------------------------------------------------------------------------

(defn- make-session-proxy-handler
  "Create a handler that validates session auth and proxies to a service."
  [{:keys [ds config]}]
  (let [services    (:services config)
        cookie-name (get-in config [:session :cookie-name] "ajet_session")
        ttl-days    (get-in config [:session :ttl-days] 30)]
    (fn [request]
      (let [auth-info (session-auth ds request cookie-name)
            trace-id  (get-in request [:headers "x-trace-id"])]
        (if auth-info
          (do
            ;; Extend session TTL asynchronously
            (auth/extend-session-ttl! ds (:session-id auth-info) ttl-days)
            ;; Proxy the request
            (proxy/proxy-request services
                                (assoc auth-info
                                  :trace-id   trace-id
                                  :remote-addr (extract-remote-addr request))
                                request))
          ;; Auth failed
          (if (is-web-route? (:uri request))
            (web-redirect-to-login)
            (json-401 trace-id)))))))

(defn- make-api-proxy-handler
  "Create a handler that validates session OR API token auth and proxies to API."
  [{:keys [ds config]}]
  (let [services    (:services config)
        cookie-name (get-in config [:session :cookie-name] "ajet_session")
        ttl-days    (get-in config [:session :ttl-days] 30)]
    (fn [request]
      (let [trace-id  (get-in request [:headers "x-trace-id"])
            ;; Try session auth first, then API token
            session   (session-auth ds request cookie-name)
            api-token (when-not session (api-token-auth ds request))
            auth-info (or session api-token)]
        (if auth-info
          (do
            ;; Extend session TTL if session auth was used
            (when (and session (:session-id session))
              (auth/extend-session-ttl! ds (:session-id session) ttl-days))
            (proxy/proxy-request services
                                (assoc auth-info
                                  :trace-id    trace-id
                                  :remote-addr (extract-remote-addr request))
                                request))
          (json-401 trace-id))))))

(defn- make-webhook-proxy-handler
  "Create a handler that validates webhook token and proxies to API."
  [{:keys [ds config]}]
  (let [services (:services config)]
    (fn [request]
      (let [trace-id  (get-in request [:headers "x-trace-id"])
            auth-info (webhook-auth ds request)]
        (if auth-info
          (proxy/proxy-request services
                              (assoc auth-info
                                :trace-id    trace-id
                                :remote-addr (extract-remote-addr request))
                              request)
          (json-401 trace-id))))))

(defn- make-tui-proxy-handler
  "Create a handler that validates session auth and proxies to TUI SM."
  [{:keys [ds config]}]
  (let [services    (:services config)
        cookie-name (get-in config [:session :cookie-name] "ajet_session")
        ttl-days    (get-in config [:session :ttl-days] 30)]
    (fn [request]
      (let [auth-info (session-auth ds request cookie-name)
            trace-id  (get-in request [:headers "x-trace-id"])]
        (if auth-info
          (do
            (auth/extend-session-ttl! ds (:session-id auth-info) ttl-days)
            (proxy/proxy-request services
                                (assoc auth-info
                                  :trace-id    trace-id
                                  :remote-addr (extract-remote-addr request))
                                request))
          (json-401 trace-id))))))

;;; ---------------------------------------------------------------------------
;;; Health check
;;; ---------------------------------------------------------------------------

(defn- health-handler
  "Health check endpoint. Verifies DB connectivity."
  [{:keys [ds]}]
  (fn [_request]
    (try
      (let [result (ajet.chat.shared.db/execute-one! ds
                     {:select [[[:raw "1"] :ok]]})]
        {:status  200
         :headers {"Content-Type" "application/json"}
         :body    "{\"status\":\"ok\",\"db\":\"connected\"}"})
      (catch Exception e
        (log/warn e "Health check — DB unreachable")
        {:status  503
         :headers {"Content-Type" "application/json"}
         :body    "{\"status\":\"degraded\",\"db\":\"disconnected\"}"}))))

;;; ---------------------------------------------------------------------------
;;; Router
;;; ---------------------------------------------------------------------------

(defn app
  "Build the full Ring handler with reitit router and middleware stack.

   sys: system map {:ds, :config, :limiter, :oauth-providers-atom, :setup-complete-atom}"
  [sys]
  (let [session-proxy (make-session-proxy-handler sys)
        api-proxy     (make-api-proxy-handler sys)
        webhook-proxy (make-webhook-proxy-handler sys)
        tui-proxy     (make-tui-proxy-handler sys)
        config        (:config sys)
        limiter       (:limiter sys)]
    (ring/ring-handler
      (ring/router
        [;; --- Self-handled: Auth ---
         ["/auth/login"
          {:get {:handler (fn [request]
                            (oauth/login-page-handler sys request))}}]

         ["/auth/callback/:provider"
          {:get {:handler (fn [request]
                            (oauth/callback-handler sys request))}}]

         ["/auth/logout"
          {:post {:handler (fn [request]
                             (oauth/logout-handler sys request))}}]

         ;; --- Self-handled: Invite ---
         ["/invite/:code"
          {:get {:handler (fn [request]
                            (oauth/invite-landing-handler sys request))}}]

         ;; --- Self-handled: Health ---
         ["/health"
          {:get {:handler (health-handler sys)}}]

         ;; --- Self-handled: Setup Wizard ---
         ;; Conditional: if setup incomplete, Auth GW renders wizard pages.
         ;; If setup complete, /setup proxies to Web SM.
         ["/setup"
          {:get {:handler (fn [request]
                            (if (setup/setup-complete? sys)
                              (session-proxy request)
                              (setup/wizard-page-handler sys request)))}}]

         ["/setup/create-community"
          {:get  {:handler (fn [request]
                             (setup/create-community-page-handler sys request))}
           :post {:handler (fn [request]
                             (setup/create-community-handler sys request))}}]

         ["/setup/providers"
          {:post {:handler (fn [request]
                             (setup/add-provider-handler sys request))}}]

         ["/setup/providers/:id/delete"
          {:post {:handler (fn [request]
                             (setup/delete-provider-handler sys request))}}]

         ;; --- Webhook proxy (before /api/* to match first) ---
         ["/api/webhooks/:webhook-id/incoming"
          {:post {:handler webhook-proxy}}]

         ;; --- API proxy (session or API token) ---
         ["/api/*"
          {:get    {:handler api-proxy}
           :post   {:handler api-proxy}
           :put    {:handler api-proxy}
           :delete {:handler api-proxy}}]

         ;; --- TUI SM proxy ---
         ["/tui/sse/*"
          {:get {:handler tui-proxy}}]

         ["/tui/*"
          {:post {:handler tui-proxy}}]

         ;; --- Web SM proxy (SSE) ---
         ["/sse/*"
          {:get {:handler session-proxy}}]

         ;; --- Web SM proxy (form posts) ---
         ["/web/*"
          {:post {:handler session-proxy}}]

         ;; --- Web SM proxy (app pages) ---
         ["/app"
          {:get {:handler session-proxy}}]
         ["/app/*"
          {:get {:handler session-proxy}}]

         ;; --- Web SM proxy (root) ---
         ["/"
          {:get {:handler session-proxy}}]]

        ;; Router options
        {:conflicts nil}) ;; Suppress conflict warnings for catch-all routes

      ;; Default handler for unmatched routes
      (ring/create-default-handler
        {:not-found (constantly
                      {:status  404
                       :headers {"Content-Type" "text/html; charset=utf-8"}
                       :body    (pages/error-page {:status  404
                                                   :title   "Not Found"
                                                   :message "The page you're looking for doesn't exist."})})})

      ;; Middleware stack (applied outermost first)
      {:middleware [;; Parse cookies
                    wrap-cookies
                    ;; Buffer body so both wrap-params and proxy can read it
                    mw/wrap-buffer-body
                    ;; Parse query params and form body
                    wrap-params
                    ;; Catch-all exception handler (outermost)
                    mw/wrap-exception-handler
                    ;; CORS
                    [mw/wrap-cors config]
                    ;; Trace ID generation
                    mw/wrap-trace-id
                    ;; Request logging
                    mw/wrap-request-logging
                    ;; Rate limiting
                    [mw/wrap-rate-limit limiter (get config :rate-limit {:enabled true})]]})))
