(ns ajet.chat.tui-sm.routes
  "Reitit router for the TUI session manager.

   All routes are under /tui. SSE events stream at /tui/sse/events,
   all other endpoints are JSON request/response.

   Middleware pipeline: exception handling -> request logging ->
   system injection -> JSON body parsing -> user context extraction ->
   route dispatch."
  (:require [reitit.ring :as ring]
            [reitit.ring.middleware.parameters :as parameters]
            [clojure.data.json :as json]
            [clojure.string :as str]
            [clojure.tools.logging :as log]
            [ajet.chat.shared.eventbus :as eventbus]
            [ajet.chat.shared.logging :as logging]
            [ajet.chat.tui-sm.sse :as sse]
            [ajet.chat.tui-sm.handlers :as handlers]))

;;; ---------------------------------------------------------------------------
;;; Response Helpers
;;; ---------------------------------------------------------------------------

(defn- json-response
  [status body]
  {:status  status
   :headers {"Content-Type" "application/json"}
   :body    (json/write-str body)})

(defn- error-response
  [status code message]
  (json-response status {:error {:code code :message message}}))

;;; ---------------------------------------------------------------------------
;;; Middleware
;;; ---------------------------------------------------------------------------

(defn- wrap-exception-handler
  "Catch-all exception handler."
  [handler]
  (fn [request]
    (try
      (handler request)
      (catch clojure.lang.ExceptionInfo e
        (let [data (ex-data e)
              typ  (:type data)]
          (cond
            (= typ :ajet.chat/validation-error)
            (error-response 422 "VALIDATION_ERROR" (ex-message e))

            (= typ :ajet.chat/api-error)
            (json-response (:status data)
                           (or (:body data)
                               {:error {:code "API_ERROR" :message (ex-message e)}}))

            :else
            (do
              (log/error e "Unhandled exception" (pr-str data))
              (error-response 500 "INTERNAL_ERROR" "An internal error occurred")))))
      (catch Exception e
        (log/error e "Unhandled exception")
        (error-response 500 "INTERNAL_ERROR" "An internal error occurred")))))

(defn- wrap-system
  "Inject system map into the request."
  [handler system]
  (fn [request]
    (handler (assoc request :system system))))

(defn- wrap-json-body
  "Parse JSON request body into :body-params."
  [handler]
  (fn [request]
    (let [content-type (get-in request [:headers "content-type"] "")]
      (if (and (:body request)
               (or (.contains content-type "application/json")
                   (.contains content-type "text/json")))
        (try
          (let [body-str (slurp (:body request))
                parsed   (when-not (str/blank? body-str)
                           (json/read-str body-str :key-fn keyword))]
            (handler (assoc request :body-params (or parsed {}))))
          (catch Exception _
            (error-response 400 "INVALID_JSON" "Request body is not valid JSON")))
        (handler (assoc request :body-params {}))))))

(defn- wrap-user-context
  "Extract user context from Auth GW headers."
  [handler]
  (fn [request]
    (let [headers (:headers request)
          user-id      (get headers "x-user-id")
          user-role    (get headers "x-user-role")
          community-id (get headers "x-community-id")]
      (handler (cond-> request
                 user-id      (assoc :user-id user-id)
                 user-role    (assoc :user-role user-role)
                 community-id (assoc :community-id community-id))))))

;;; ---------------------------------------------------------------------------
;;; Health Check
;;; ---------------------------------------------------------------------------

(defn health-check
  "GET /tui/health - Health check endpoint."
  [request]
  (let [nats-conn (get-in request [:system :nats])
        nats-ok   (try
                    (and nats-conn
                         (eventbus/connected? nats-conn))
                    (catch Exception _ false))]
    (json-response (if nats-ok 200 503)
                   {:status      (if nats-ok "healthy" "degraded")
                    :connections (sse/connection-count)
                    :nats        (if nats-ok "connected" "disconnected")})))

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

(defn router
  "Build the reitit router with all TUI routes."
  []
  (ring/router
    [["/tui"

      ;; Health check - no auth required
      ["/health" {:get {:handler health-check}}]

      ;; SSE event stream
      ["/sse/events" {:get {:handler sse/sse-handler}}]

      ;; Messages
      ["/messages"
       ["" {:get  {:handler handlers/fetch-messages}
            :post {:handler handlers/send-message}}]
       ["/:id/edit" {:post {:handler handlers/edit-message}}]
       ["/:id/delete" {:post {:handler handlers/delete-message}}]]

      ;; Reactions
      ["/reactions"
       ["" {:post {:handler handlers/add-reaction}}]
       ["/remove" {:post {:handler handlers/remove-reaction}}]]

      ;; Navigation
      ["/navigate" {:post {:handler handlers/navigate}}]

      ;; Read tracking
      ["/read" {:post {:handler handlers/mark-read}}]

      ;; Typing indicator
      ["/typing" {:post {:handler handlers/typing}}]

      ;; Heartbeat
      ["/heartbeat" {:post {:handler handlers/heartbeat}}]

      ;; Search
      ["/search" {:get {:handler handlers/search}}]

      ;; Slash commands
      ["/command" {:post {:handler handlers/slash-command}}]]]

    {:data {:middleware [parameters/parameters-middleware]}}))

;;; ---------------------------------------------------------------------------
;;; App
;;; ---------------------------------------------------------------------------

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

   system: map with :config, :nats, :connections keys."
  [system]
  (let [handler (ring/ring-handler
                  (router)
                  (ring/create-default-handler
                    {:not-found
                     (constantly (error-response 404 "NOT_FOUND" "Route not found"))
                     :method-not-allowed
                     (constantly (error-response 405 "METHOD_NOT_ALLOWED" "Method not allowed"))}))]
    (-> handler
        wrap-user-context
        wrap-json-body
        (wrap-system system)
        logging/wrap-request-logging
        wrap-exception-handler)))
