(ns ajet.chat.tui-sm.handlers
  "Signal handlers for TUI session manager.

   All POST/GET /tui/* handlers (except SSE and health). Each handler
   parses the JSON body, calls the API via the shared api-client, and
   returns a JSON response.

   Auth is handled by Auth GW upstream -- these handlers receive
   X-User-Id, X-User-Role headers on every request."
  (:require [clojure.data.json :as json]
            [clojure.tools.logging :as log]
            [ajet.chat.shared.api-client :as api]
            [ajet.chat.shared.eventbus :as eventbus]
            [ajet.chat.tui-sm.sse :as sse]))

;;; ---------------------------------------------------------------------------
;;; 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."
  ([status code message]
   (json-response status {:error {:code code :message message}}))
  ([status code message details]
   (json-response status {:error {:code code :message message :details details}})))

(defn- require-user-id
  "Check that user-id is present on the request. Returns nil if valid,
   or an error response if missing."
  [request]
  (when-not (:user-id request)
    (error-response 401 "UNAUTHORIZED" "Missing X-User-Id header")))

(defn- api-ctx
  "Build API client context from the request."
  [request]
  (let [config   (get-in request [:system :config])
        base-url (get-in config [:api :base-url] "http://localhost:3001")]
    (api/request->ctx request base-url)))

(defn- with-api-call
  "Execute an API call, returning the result as JSON or an error response."
  [request api-fn]
  (or (require-user-id request)
      (try
        (let [result (api-fn (api-ctx request))]
          (json-response 200 result))
        (catch clojure.lang.ExceptionInfo e
          (let [data (ex-data e)]
            (if (= :ajet.chat/api-error (:type data))
              (json-response (:status data) (or (:body data)
                                                {:error {:code    "API_ERROR"
                                                         :message (ex-message e)}}))
              (do
                (log/error e "Handler error" (pr-str data))
                (error-response 500 "INTERNAL_ERROR" "An internal error occurred")))))
        (catch Exception e
          (log/error e "Unexpected handler error")
          (error-response 500 "INTERNAL_ERROR" "An internal error occurred")))))

;;; ---------------------------------------------------------------------------
;;; Message Handlers
;;; ---------------------------------------------------------------------------

(defn send-message
  "POST /tui/messages - Send a message to a channel.

   Body: {:channel-id uuid, :body-md string, :parent-id uuid?}"
  [request]
  (with-api-call request
    (fn [ctx]
      (let [body       (:body-params request)
            channel-id (:channel-id body)]
        (when-not channel-id
          (throw (ex-info "channel-id is required"
                          {:type :ajet.chat/validation-error})))
        (when-not (:body-md body)
          (throw (ex-info "body-md is required"
                          {:type :ajet.chat/validation-error})))
        (api/send-message ctx channel-id
                          (select-keys body [:body-md :parent-id]))))))

(defn edit-message
  "POST /tui/messages/:id/edit - Edit an existing message.

   Body: {:body-md string}"
  [request]
  (with-api-call request
    (fn [ctx]
      (let [message-id (get-in request [:path-params :id])
            body       (:body-params request)]
        (when-not (:body-md body)
          (throw (ex-info "body-md is required"
                          {:type :ajet.chat/validation-error})))
        (api/edit-message ctx message-id (select-keys body [:body-md]))))))

(defn delete-message
  "POST /tui/messages/:id/delete - Delete a message.

   No body required."
  [request]
  (with-api-call request
    (fn [ctx]
      (let [message-id (get-in request [:path-params :id])]
        (api/delete-message ctx message-id)))))

;;; ---------------------------------------------------------------------------
;;; Reaction Handlers
;;; ---------------------------------------------------------------------------

(defn add-reaction
  "POST /tui/reactions - Add a reaction to a message.

   Body: {:message-id uuid, :emoji string}"
  [request]
  (with-api-call request
    (fn [ctx]
      (let [body       (:body-params request)
            message-id (:message-id body)
            emoji      (:emoji body)]
        (when-not message-id
          (throw (ex-info "message-id is required"
                          {:type :ajet.chat/validation-error})))
        (when-not emoji
          (throw (ex-info "emoji is required"
                          {:type :ajet.chat/validation-error})))
        (api/add-reaction ctx message-id emoji)))))

(defn remove-reaction
  "POST /tui/reactions/remove - Remove a reaction from a message.

   Body: {:message-id uuid, :emoji string}"
  [request]
  (with-api-call request
    (fn [ctx]
      (let [body       (:body-params request)
            message-id (:message-id body)
            emoji      (:emoji body)]
        (when-not message-id
          (throw (ex-info "message-id is required"
                          {:type :ajet.chat/validation-error})))
        (when-not emoji
          (throw (ex-info "emoji is required"
                          {:type :ajet.chat/validation-error})))
        (api/remove-reaction ctx message-id emoji)))))

;;; ---------------------------------------------------------------------------
;;; Navigation
;;; ---------------------------------------------------------------------------

(defn navigate
  "POST /tui/navigate - Switch the user's active community/channel.

   Body: {:community-id uuid, :channel-id uuid?}

   Updates connection tracking and re-subscribes NATS subjects.
   Returns the channel's messages if a channel-id is provided."
  [request]
  (or (require-user-id request)
      (try
        (let [ctx          (api-ctx request)
              body         (:body-params request)
              user-id      (:user-id request)
              community-id (:community-id body)
              channel-id   (:channel-id body)
              nats-conn    (get-in request [:system :nats])
              config       (get-in request [:system :config])
              bp-limit     (get-in config [:sse :backpressure-limit] 1000)]
          (when-not community-id
            (throw (ex-info "community-id is required"
                            {:type :ajet.chat/validation-error})))

          ;; Re-subscribe NATS to the new community
          (sse/resubscribe! nats-conn user-id community-id bp-limit)

          ;; Update active channel in connection tracker
          (when (sse/get-connection user-id)
            (swap! sse/connections assoc-in [user-id :active-channel] channel-id))

          ;; Fetch channel messages if channel-id provided
          (let [result (if channel-id
                         (let [messages (api/get-messages ctx channel-id {:limit 50})]
                           {:community-id community-id
                            :channel-id   channel-id
                            :messages     messages})
                         {:community-id community-id
                          :channel-id   nil
                          :messages     []})]
            (json-response 200 result)))
        (catch clojure.lang.ExceptionInfo e
          (let [data (ex-data e)]
            (if (= :ajet.chat/api-error (:type data))
              (json-response (:status data) (or (:body data)
                                                {:error {:code    "API_ERROR"
                                                         :message (ex-message e)}}))
              (if (= :ajet.chat/validation-error (:type data))
                (error-response 422 "VALIDATION_ERROR" (ex-message e))
                (do
                  (log/error e "Navigate error" (pr-str data))
                  (error-response 500 "INTERNAL_ERROR" "An internal error occurred"))))))
        (catch Exception e
          (log/error e "Unexpected navigate error")
          (error-response 500 "INTERNAL_ERROR" "An internal error occurred")))))

;;; ---------------------------------------------------------------------------
;;; Read Tracking
;;; ---------------------------------------------------------------------------

(defn mark-read
  "POST /tui/read - Mark a channel as read.

   Body: {:channel-id uuid, :last-message-id uuid?}"
  [request]
  (with-api-call request
    (fn [ctx]
      (let [body       (:body-params request)
            channel-id (:channel-id body)]
        (when-not channel-id
          (throw (ex-info "channel-id is required"
                          {:type :ajet.chat/validation-error})))
        (api/mark-read ctx channel-id
                       (select-keys body [:last-message-id]))))))

;;; ---------------------------------------------------------------------------
;;; Typing Indicator
;;; ---------------------------------------------------------------------------

(defn typing
  "POST /tui/typing - Send a typing indicator.

   Body: {:channel-id uuid, :community-id uuid}

   Publishes directly to NATS for low latency (bypasses API)."
  [request]
  (or (require-user-id request)
      (try
        (let [body         (:body-params request)
              user-id      (:user-id request)
              channel-id   (:channel-id body)
              community-id (:community-id body)
              nats-conn    (get-in request [:system :nats])]
          (when-not channel-id
            (throw (ex-info "channel-id is required"
                            {:type :ajet.chat/validation-error})))
          (when-not community-id
            (throw (ex-info "community-id is required"
                            {:type :ajet.chat/validation-error})))

          ;; Publish typing indicator directly to NATS
          (eventbus/publish! nats-conn
            (str "chat.typing." community-id "." channel-id)
            :typing/started
            {:user-id      user-id
             :channel-id   channel-id
             :community-id community-id})

          (json-response 200 {:ok true}))
        (catch clojure.lang.ExceptionInfo e
          (let [data (ex-data e)]
            (if (= :ajet.chat/validation-error (:type data))
              (error-response 422 "VALIDATION_ERROR" (ex-message e))
              (do
                (log/error e "Typing handler error")
                (error-response 500 "INTERNAL_ERROR" "An internal error occurred")))))
        (catch Exception e
          (log/error e "Unexpected typing error")
          (error-response 500 "INTERNAL_ERROR" "An internal error occurred")))))

;;; ---------------------------------------------------------------------------
;;; Heartbeat
;;; ---------------------------------------------------------------------------

(defn heartbeat
  "POST /tui/heartbeat - Client heartbeat. Proxied to the API."
  [request]
  (with-api-call request
    (fn [ctx]
      (api/heartbeat ctx))))

;;; ---------------------------------------------------------------------------
;;; Fetch Messages
;;; ---------------------------------------------------------------------------

(defn fetch-messages
  "GET /tui/messages - Fetch messages for a channel (paginated).

   Query params: channel-id (required), before (uuid?), limit (int?)"
  [request]
  (with-api-call request
    (fn [ctx]
      (let [params     (:query-params request)
            channel-id (get params "channel-id")]
        (when-not channel-id
          (throw (ex-info "channel-id query parameter is required"
                          {:type :ajet.chat/validation-error})))
        (api/get-messages ctx channel-id
                          (cond-> {}
                            (get params "before") (assoc :before (get params "before"))
                            (get params "limit")  (assoc :limit (parse-long (get params "limit")))))))))

;;; ---------------------------------------------------------------------------
;;; Search
;;; ---------------------------------------------------------------------------

(defn search
  "GET /tui/search - Search messages. Proxied to API search.

   Query params: q (required), type, community-id, channel-id, from, after, before, limit"
  [request]
  (with-api-call request
    (fn [ctx]
      (let [params (:query-params request)
            q      (get params "q")]
        (when-not q
          (throw (ex-info "q query parameter is required"
                          {:type :ajet.chat/validation-error})))
        (api/search ctx
          (cond-> {:q q}
            (get params "type")         (assoc :type (keyword (get params "type")))
            (get params "community-id") (assoc :community-id (get params "community-id"))
            (get params "channel-id")   (assoc :channel-id (get params "channel-id"))
            (get params "from")         (assoc :from (get params "from"))
            (get params "after")        (assoc :after (get params "after"))
            (get params "before")       (assoc :before (get params "before"))
            (get params "limit")        (assoc :limit (parse-long (get params "limit")))))))))

;;; ---------------------------------------------------------------------------
;;; Slash Commands
;;; ---------------------------------------------------------------------------

(defn slash-command
  "POST /tui/command - Execute a slash command. Proxied to API.

   Body: {:command string, :args string?, :channel-id uuid?, :community-id uuid?}"
  [request]
  (with-api-call request
    (fn [ctx]
      (let [body (:body-params request)]
        (when-not (:command body)
          (throw (ex-info "command is required"
                          {:type :ajet.chat/validation-error})))
        (api/execute-command ctx body)))))
