(ns ajet.chat.api.handlers.presence
  "Presence and heartbeat handlers.

   Users send heartbeats every 60 seconds. A user is considered online
   if their last heartbeat was within 2 minutes. Heartbeat responses
   are immediate; DB update and NATS publishing happen asynchronously."
  (:require [clojure.tools.logging :as log]
            [ajet.chat.shared.db :as db]
            [ajet.chat.shared.eventbus :as eventbus]
            [ajet.chat.api.middleware :as mw]))

;;; ---------------------------------------------------------------------------
;;; Helpers
;;; ---------------------------------------------------------------------------

(defn- require-user! [request]
  (or (:user-id request)
      (throw (ex-info "Authentication required" {:type :ajet.chat/forbidden}))))

(defn- check-membership! [ds community-id user-id]
  (or (db/execute-one! ds
        {:select [:user-id]
         :from   [:community-members]
         :where  [:and
                  [:= :community-id [:cast community-id :uuid]]
                  [:= :user-id [:cast user-id :uuid]]]})
      (throw (ex-info "Not a member of this community"
                      {:type :ajet.chat/forbidden}))))

;;; ---------------------------------------------------------------------------
;;; Handlers
;;; ---------------------------------------------------------------------------

(defn heartbeat
  "POST /api/heartbeat
   Reports online status. Responds immediately, updates DB async."
  [request]
  (let [user-id (require-user! request)
        ds      (get-in request [:system :ds])
        nats    (get-in request [:system :nats])]

    ;; Update last_seen_at asynchronously
    (future
      (try
        (db/execute! ds
          {:update :users
           :set    {:last-seen-at [:now]}
           :where  [:= :id [:cast user-id :uuid]]})

        ;; Publish presence event to all communities the user belongs to
        (let [communities (db/execute! ds
                            {:select [:community-id]
                             :from   [:community-members]
                             :where  [:= :user-id [:cast user-id :uuid]]})]
          (doseq [{:keys [community-id]} communities]
            (try
              (eventbus/publish! nats
                (str "chat.presence." community-id)
                :presence/online
                {:user-id user-id
                 :community-id (str community-id)})
              (catch Exception e
                (log/error e "Failed to publish presence event")))))
        (catch Exception e
          (log/error e "Failed to process heartbeat for user" user-id))))

    ;; Respond immediately
    (mw/json-response {:status "ok"})))

(defn get-presence
  "GET /api/communities/:cid/presence
   Returns list of online members (last heartbeat within 2 minutes)."
  [request]
  (let [user-id      (require-user! request)
        ds           (get-in request [:system :ds])
        community-id (get-in request [:path-params :cid])
        _            (check-membership! ds community-id user-id)

        ;; Online = last_seen_at within 2 minutes
        online-members (db/execute! ds
                         {:select [:u.id :u.username :u.display-name :u.avatar-url
                                   :u.status-text :u.last-seen-at]
                          :from   [[:community-members :cm]]
                          :join   [[:users :u] [:= :u.id :cm.user-id]]
                          :where  [:and
                                   [:= :cm.community-id [:cast community-id :uuid]]
                                   [:!= :u.last-seen-at nil]
                                   [:> :u.last-seen-at
                                    [:raw "now() - interval '2 minutes'"]]]})]
    (mw/json-response online-members)))
