(ns ajet.chat.api.handlers.dms
  "DM (Direct Message) handlers.

   DMs are channels with type 'dm' or 'group_dm' and a NULL community_id.
   Creating a 1:1 DM is idempotent - returns existing if one exists
   between the two users."
  (:require [clojure.string :as str]
            [clojure.tools.logging :as log]
            [ajet.chat.shared.db :as db]
            [ajet.chat.api.middleware :as mw])
  (:import [java.util UUID]))

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

(defn- uuid [] (str (UUID/randomUUID)))

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

(defn- channel-with-members
  "Fetch a DM channel with its members."
  [ds channel-id]
  (let [channel (db/execute-one! ds
                  {:select [:*]
                   :from   [:channels]
                   :where  [:= :id [:cast channel-id :uuid]]})
        members (db/execute! ds
                  {:select [:u.id :u.username :u.display-name :u.avatar-url]
                   :from   [[:channel-members :cm]]
                   :join   [[:users :u] [:= :u.id :cm.user-id]]
                   :where  [:= :cm.channel-id [:cast channel-id :uuid]]})]
    (assoc channel :members members)))

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

(defn list-dms
  "GET /api/dms
   Lists all DM/group_dm channels the user is a member of.
   Ordered by most recent message activity."
  [request]
  (let [user-id (require-user! request)
        ds      (get-in request [:system :ds])
        ;; Get DM channels the user is in, ordered by latest message
        channels (db/execute! ds
                   {:select [:c.*]
                    :from   [[:channels :c]]
                    :join   [[:channel-members :cm]
                             [:= :cm.channel-id :c.id]]
                    :where  [:and
                             [:= :cm.user-id [:cast user-id :uuid]]
                             [:in :c.type ["dm" "group_dm"]]]
                    :order-by [[:c.created-at :desc]]})
        ;; Enrich with member info
        result (mapv (fn [ch]
                       (let [members (db/execute! ds
                                       {:select [:u.id :u.username :u.display-name :u.avatar-url]
                                        :from   [[:channel-members :chm]]
                                        :join   [[:users :u] [:= :u.id :chm.user-id]]
                                        :where  [:= :chm.channel-id (:id ch)]})]
                         (assoc ch :members members)))
                 channels)]
    (mw/json-response result)))

(defn create-dm
  "POST /api/dms
   Creates a 1:1 DM channel, or returns existing one if it already exists."
  [request]
  (let [user-id   (require-user! request)
        ds        (get-in request [:system :ds])
        params    (:body-params request)
        target-id (:user_id params)]

    (when (str/blank? target-id)
      (throw (ex-info "user_id is required"
                      {:type :ajet.chat/validation-error})))

    (when (= user-id target-id)
      (throw (ex-info "Cannot create a DM with yourself"
                      {:type :ajet.chat/validation-error})))

    ;; Check target user exists
    (when-not (db/execute-one! ds
                {:select [:id]
                 :from   [:users]
                 :where  [:= :id [:cast target-id :uuid]]})
      (throw (ex-info "Target user not found"
                      {:type :ajet.chat/not-found})))

    ;; Check for existing DM between these two users
    (let [existing (db/execute-one! ds
                     {:select [:c.id]
                      :from   [[:channels :c]]
                      :join   [[:channel-members :cm1]
                               [:= :cm1.channel-id :c.id]
                               [:channel-members :cm2]
                               [:= :cm2.channel-id :c.id]]
                      :where  [:and
                               [:= :c.type "dm"]
                               [:= :cm1.user-id [:cast user-id :uuid]]
                               [:= :cm2.user-id [:cast target-id :uuid]]]})]
      (if existing
        ;; Return existing DM
        (mw/json-response (channel-with-members ds (str (:id existing))))

        ;; Create new DM
        (let [channel-id (uuid)]
          (db/with-transaction [tx ds]
            (db/execute! tx
              {:insert-into :channels
               :values [{:id         [:cast channel-id :uuid]
                         :name       "dm"
                         :type       "dm"
                         :visibility "private"}]})
            (db/execute! tx
              {:insert-into :channel-members
               :values [{:channel-id [:cast channel-id :uuid]
                         :user-id    [:cast user-id :uuid]}
                        {:channel-id [:cast channel-id :uuid]
                         :user-id    [:cast target-id :uuid]}]}))

          (mw/json-response 201 (channel-with-members ds channel-id)))))))

(defn create-group-dm
  "POST /api/dms/group
   Creates a group DM with multiple users."
  [request]
  (let [user-id  (require-user! request)
        ds       (get-in request [:system :ds])
        params   (:body-params request)
        user-ids (:user_ids params)]

    (when (or (nil? user-ids) (< (count user-ids) 2))
      (throw (ex-info "At least 2 other user IDs are required for a group DM"
                      {:type :ajet.chat/validation-error})))

    ;; Ensure all members include the creator
    (let [all-member-ids (distinct (cons user-id user-ids))
          channel-id     (uuid)]

      ;; Verify all users exist
      (doseq [uid user-ids]
        (when-not (db/execute-one! ds
                    {:select [:id]
                     :from   [:users]
                     :where  [:= :id [:cast uid :uuid]]})
          (throw (ex-info (str "User not found: " uid)
                          {:type :ajet.chat/not-found}))))

      (db/with-transaction [tx ds]
        (db/execute! tx
          {:insert-into :channels
           :values [{:id         [:cast channel-id :uuid]
                     :name       "group-dm"
                     :type       "group_dm"
                     :visibility "private"}]})

        (db/execute! tx
          {:insert-into :channel-members
           :values (mapv (fn [uid]
                           {:channel-id [:cast channel-id :uuid]
                            :user-id    [:cast uid :uuid]})
                     all-member-ids)}))

      (mw/json-response 201 (channel-with-members ds channel-id)))))
