(ns ajet.chat.api.handlers.users
  "User profile and community member management handlers.

   Covers: get/update own profile, view other users, list community members,
   update member roles/nicknames, and kick members."
  (: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 [:*]
         :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}))))

(defn- check-role! [ds community-id user-id required-role]
  (let [member (check-membership! ds community-id user-id)
        role   (:role member)
        hierarchy {"owner" 3 "admin" 2 "member" 1}
        has-level (get hierarchy role 0)
        need-level (get hierarchy required-role 0)]
    (when (< has-level need-level)
      (throw (ex-info (str "Requires " required-role " role or higher")
                      {:type :ajet.chat/forbidden})))
    member))

(defn- publish-event! [nats subject event-type payload]
  (try
    (eventbus/publish! nats subject event-type payload)
    (catch Exception e
      (log/error e "Failed to publish event" event-type "to" subject))))

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

(defn get-me
  "GET /api/me
   Returns the authenticated user's profile."
  [request]
  (let [user-id (require-user! request)
        ds      (get-in request [:system :ds])
        user    (db/execute-one! ds
                  {:select [:id :username :display-name :email :avatar-url
                            :status-text :created-at]
                   :from   [:users]
                   :where  [:= :id [:cast user-id :uuid]]})]
    (if user
      (mw/json-response user)
      (mw/error-response 404 "NOT_FOUND" "User not found"))))

(defn update-me
  "PUT /api/me
   Updates display_name and/or status_text."
  [request]
  (let [user-id (require-user! request)
        ds      (get-in request [:system :ds])
        params  (:body-params request)
        updates (cond-> {}
                  (contains? params :display_name) (assoc :display-name (:display_name params))
                  (contains? params :status_text)  (assoc :status-text (:status_text params)))]
    (when (seq updates)
      (db/execute! ds
        {:update :users
         :set    updates
         :where  [:= :id [:cast user-id :uuid]]}))

    (let [user (db/execute-one! ds
                 {:select [:id :username :display-name :email :avatar-url
                           :status-text :created-at]
                  :from   [:users]
                  :where  [:= :id [:cast user-id :uuid]]})]
      (mw/json-response user))))

(defn get-user
  "GET /api/users/:id
   Returns public profile for any user."
  [request]
  (let [_       (require-user! request)
        ds      (get-in request [:system :ds])
        user-id (get-in request [:path-params :id])
        user    (db/execute-one! ds
                  {:select [:id :username :display-name :avatar-url :status-text :created-at]
                   :from   [:users]
                   :where  [:= :id [:cast user-id :uuid]]})]
    (if user
      (mw/json-response user)
      (mw/error-response 404 "NOT_FOUND" "User not found"))))

(defn list-community-members
  "GET /api/communities/:cid/members
   Lists all members of a community with roles and nicknames."
  [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)
        members      (db/execute! ds
                       {:select [:u.id :u.username :u.display-name :u.avatar-url
                                 :u.status-text :cm.role :cm.nickname :cm.joined-at]
                        :from   [[:community-members :cm]]
                        :join   [[:users :u] [:= :u.id :cm.user-id]]
                        :where  [:= :cm.community-id [:cast community-id :uuid]]
                        :order-by [[:u.username :asc]]})]
    (mw/json-response members)))

(defn update-community-member
  "PUT /api/communities/:cid/members/:uid
   Update a member's nickname or role. Admin+ for nickname, Owner for role."
  [request]
  (let [user-id      (require-user! request)
        ds           (get-in request [:system :ds])
        nats         (get-in request [:system :nats])
        community-id (get-in request [:path-params :cid])
        target-uid   (get-in request [:path-params :uid])
        params       (:body-params request)
        actor-member (check-role! ds community-id user-id "admin")]

    ;; Verify target is a member
    (let [target-member (check-membership! ds community-id target-uid)]

      ;; Role changes require owner permission
      (when (contains? params :role)
        (when (not= (:role actor-member) "owner")
          (throw (ex-info "Only the owner can change roles"
                          {:type :ajet.chat/forbidden})))
        ;; Cannot change owner's role
        (when (= (:role target-member) "owner")
          (throw (ex-info "Cannot change the owner's role"
                          {:type :ajet.chat/forbidden}))))

      (let [updates (cond-> {}
                      (contains? params :nickname) (assoc :nickname (:nickname params))
                      (contains? params :role)     (assoc :role (:role params)))]
        (when (seq updates)
          (db/execute! ds
            {:update :community-members
             :set    updates
             :where  [:and
                      [:= :community-id [:cast community-id :uuid]]
                      [:= :user-id [:cast target-uid :uuid]]]}))

        (let [updated (db/execute-one! ds
                        {:select [:u.id :u.username :u.display-name :cm.role :cm.nickname]
                         :from   [[:community-members :cm]]
                         :join   [[:users :u] [:= :u.id :cm.user-id]]
                         :where  [:and
                                  [:= :cm.community-id [:cast community-id :uuid]]
                                  [:= :cm.user-id [:cast target-uid :uuid]]]})]
          (mw/json-response updated))))))

(defn kick-member
  "DELETE /api/communities/:cid/members/:uid
   Removes a member from the community. Admin+. Cannot kick owner."
  [request]
  (let [user-id      (require-user! request)
        ds           (get-in request [:system :ds])
        nats         (get-in request [:system :nats])
        community-id (get-in request [:path-params :cid])
        target-uid   (get-in request [:path-params :uid])]

    (check-role! ds community-id user-id "admin")

    ;; Cannot kick owner
    (let [target-member (check-membership! ds community-id target-uid)]
      (when (= (:role target-member) "owner")
        (throw (ex-info "Cannot kick the community owner"
                        {:type :ajet.chat/forbidden}))))

    ;; Remove from all channels in this community
    (db/execute-sql! ds
      ["DELETE FROM channel_members WHERE user_id = ?::uuid AND channel_id IN (SELECT id FROM channels WHERE community_id = ?::uuid)"
       target-uid community-id])

    ;; Remove community membership
    (db/execute! ds
      {:delete-from :community-members
       :where       [:and
                     [:= :community-id [:cast community-id :uuid]]
                     [:= :user-id [:cast target-uid :uuid]]]})

    (publish-event! nats
      (str "chat.events." community-id)
      :member/kicked
      {:community-id community-id
       :user-id      target-uid
       :kicked-by    user-id})

    (mw/json-response 204 nil)))
