(ns ajet.chat.api.middleware
  "Ring middleware pipeline for the API service.

   Provides:
   - Exception handling (catch-all -> 500)
   - User context extraction from Auth GW headers
   - Ban check for community-scoped endpoints
   - Mute check for write endpoints
   - JSON response helpers"
  (:require [clojure.data.json :as json]
            [clojure.string :as str]
            [clojure.tools.logging :as log]
            [ajet.chat.shared.db :as db]))

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

(defn json-response
  "Build a Ring response with JSON content type."
  ([status body]
   {:status  status
    :headers {"Content-Type" "application/json"}
    :body    (json/write-str body)})
  ([body]
   (json-response 200 body)))

(defn error-response
  "Build a JSON error response.
   code: string like \"NOT_FOUND\", \"FORBIDDEN\"
   message: human-readable string
   details: optional map of additional context"
  ([status code message]
   (error-response status code message {}))
  ([status code message details]
   (json-response status {:error {:code    code
                                  :message message
                                  :details details}})))

;;; ---------------------------------------------------------------------------
;;; Middleware: Exception Handler
;;; ---------------------------------------------------------------------------

(defn wrap-exception-handler
  "Catch-all exception handler. Returns 500 JSON error for unhandled exceptions."
  [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) (dissoc data :type))

            (= typ :ajet.chat/not-found)
            (error-response 404 "NOT_FOUND" (ex-message e) (dissoc data :type))

            (= typ :ajet.chat/forbidden)
            (error-response 403 "FORBIDDEN" (ex-message e) (dissoc data :type))

            (= typ :ajet.chat/conflict)
            (error-response 409 "CONFLICT" (ex-message e) (dissoc data :type))

            (= typ :ajet.chat/gone)
            (error-response 410 "GONE" (ex-message e) (dissoc data :type))

            :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")))))

;;; ---------------------------------------------------------------------------
;;; Middleware: User Context
;;; ---------------------------------------------------------------------------

(defn wrap-user-context
  "Extract X-User-Id, X-User-Role, X-Community-Id from request headers
   into the request map as :user-id, :user-role, :community-id."
  [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))))))

;;; ---------------------------------------------------------------------------
;;; Middleware: System Injection
;;; ---------------------------------------------------------------------------

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

;;; ---------------------------------------------------------------------------
;;; Middleware: Ban Check
;;; ---------------------------------------------------------------------------

(defn wrap-ban-check
  "For community-scoped endpoints, check if the user is banned.
   Rejects with 403 if banned. Requires :user-id and :community-id on request."
  [handler]
  (fn [request]
    (let [user-id      (:user-id request)
          community-id (or (:community-id request)
                           (get-in request [:path-params :cid]))
          ds           (get-in request [:system :ds])]
      (if (and user-id community-id ds)
        (let [banned? (db/execute-one! ds
                        {:select [[:1 :exists]]
                         :from   [:bans]
                         :where  [:and
                                  [:= :community-id [:cast community-id :uuid]]
                                  [:= :user-id [:cast user-id :uuid]]]})]
          (if banned?
            (error-response 403 "BANNED" "You are banned from this community")
            (handler request)))
        (handler request)))))

;;; ---------------------------------------------------------------------------
;;; Middleware: Mute Check
;;; ---------------------------------------------------------------------------

(defn wrap-mute-check
  "For write endpoints (POST/PUT/DELETE on messages, reactions, etc.),
   check if the user is muted. Rejects with 403 if actively muted."
  [handler]
  (fn [request]
    (let [user-id      (:user-id request)
          community-id (or (:community-id request)
                           (get-in request [:path-params :cid]))
          ds           (get-in request [:system :ds])]
      (if (and user-id community-id ds)
        (let [muted? (db/execute-one! ds
                       {:select [[:1 :exists]]
                        :from   [:mutes]
                        :where  [:and
                                 [:= :community-id [:cast community-id :uuid]]
                                 [:= :user-id [:cast user-id :uuid]]
                                 [:or
                                  [:= :expires-at nil]
                                  [:> :expires-at [:now]]]]})]
          (if muted?
            (error-response 403 "MUTED" "You are muted in this community")
            (handler request)))
        (handler request)))))

;;; ---------------------------------------------------------------------------
;;; Middleware: JSON Body Parsing
;;; ---------------------------------------------------------------------------

(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 {}))))))
