(ns ajet.chat.api.handlers.categories
  "Channel category CRUD handlers.

   Categories organize channels within a community. Each has a name
   and a position for ordering. Deleting a category sets channels
   in it to uncategorized (category_id = NULL)."
  (: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- 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))

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

(defn list-categories
  "GET /api/communities/:cid/categories
   Returns categories ordered by position."
  [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)
        categories   (db/execute! ds
                       {:select [:*]
                        :from   [:channel-categories]
                        :where  [:= :community-id [:cast community-id :uuid]]
                        :order-by [[:position :asc]]})]
    (mw/json-response categories)))

(defn create-category
  "POST /api/communities/:cid/categories
   Creates a new category. Admin+ only."
  [request]
  (let [user-id      (require-user! request)
        ds           (get-in request [:system :ds])
        community-id (get-in request [:path-params :cid])
        params       (:body-params request)
        name-v       (:name params)
        position     (or (:position params) 0)]

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

    (when (str/blank? name-v)
      (throw (ex-info "Category name is required"
                      {:type :ajet.chat/validation-error})))

    (let [category-id (uuid)]
      (db/execute! ds
        {:insert-into :channel-categories
         :values [{:id           [:cast category-id :uuid]
                   :community-id [:cast community-id :uuid]
                   :name         name-v
                   :position     position}]})

      (let [category (db/execute-one! ds
                       {:select [:*]
                        :from   [:channel-categories]
                        :where  [:= :id [:cast category-id :uuid]]})]
        (mw/json-response 201 category)))))

(defn update-category
  "PUT /api/categories/:id
   Updates category name and/or position. Admin+ only."
  [request]
  (let [user-id     (require-user! request)
        ds          (get-in request [:system :ds])
        category-id (get-in request [:path-params :id])
        params      (:body-params request)
        category    (or (db/execute-one! ds
                          {:select [:*]
                           :from   [:channel-categories]
                           :where  [:= :id [:cast category-id :uuid]]})
                        (throw (ex-info "Category not found"
                                        {:type :ajet.chat/not-found})))
        community-id (str (:community-id category))]

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

    (let [updates (cond-> {}
                    (:name params)                  (assoc :name (:name params))
                    (contains? params :position)    (assoc :position (:position params)))]
      (when (seq updates)
        (db/execute! ds
          {:update :channel-categories
           :set    updates
           :where  [:= :id [:cast category-id :uuid]]}))

      (let [updated (db/execute-one! ds
                      {:select [:*]
                       :from   [:channel-categories]
                       :where  [:= :id [:cast category-id :uuid]]})]
        (mw/json-response updated)))))

(defn delete-category
  "DELETE /api/categories/:id
   Deletes category. Channels become uncategorized (ON DELETE SET NULL). Admin+ only."
  [request]
  (let [user-id     (require-user! request)
        ds          (get-in request [:system :ds])
        category-id (get-in request [:path-params :id])
        category    (or (db/execute-one! ds
                          {:select [:*]
                           :from   [:channel-categories]
                           :where  [:= :id [:cast category-id :uuid]]})
                        (throw (ex-info "Category not found"
                                        {:type :ajet.chat/not-found})))
        community-id (str (:community-id category))]

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

    ;; ON DELETE SET NULL on channels.category_id handles the uncategorizing
    (db/execute! ds
      {:delete-from :channel-categories
       :where       [:= :id [:cast category-id :uuid]]})

    (mw/json-response 204 nil)))
