(ns ajet.chat.api.handlers.communities
  "Community CRUD handlers.

   Communities are the top-level organizational unit. Creating a community
   makes the creator the owner and bootstraps a #general channel."
  (:require [clojure.string :as str]
            [clojure.tools.logging :as log]
            [ajet.chat.shared.db :as db]
            [ajet.chat.shared.eventbus :as eventbus]
            [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!
  "Verify user is a member of the community. Returns the membership row."
  [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!
  "Verify user has the required role or higher. Returns membership row."
  [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 create-community
  "POST /api/communities
   Creates community, adds user as owner, creates #general channel."
  [request]
  (let [user-id (require-user! request)
        ds      (get-in request [:system :ds])
        nats    (get-in request [:system :nats])
        params  (:body-params request)
        name-v  (:name params)
        slug    (:slug params)]

    ;; Validate required fields
    (when (or (str/blank? name-v) (str/blank? slug))
      (throw (ex-info "Name and slug are required"
                      {:type :ajet.chat/validation-error})))

    ;; Validate slug format
    (when-not (re-matches #"[a-z0-9][a-z0-9-]*[a-z0-9]" slug)
      (throw (ex-info "Slug must be lowercase letters, digits, and hyphens"
                      {:type :ajet.chat/validation-error})))

    ;; Check slug uniqueness
    (when (db/execute-one! ds
            {:select [:id]
             :from   [:communities]
             :where  [:= :slug slug]})
      (throw (ex-info "A community with this slug already exists"
                      {:type :ajet.chat/conflict})))

    (let [community-id (uuid)
          channel-id   (uuid)]
      (db/with-transaction [tx ds]
        ;; Create community
        (db/execute! tx
          {:insert-into :communities
           :values [{:id   [:cast community-id :uuid]
                     :name name-v
                     :slug slug}]})

        ;; Add creator as owner
        (db/execute! tx
          {:insert-into :community-members
           :values [{:community-id [:cast community-id :uuid]
                     :user-id      [:cast user-id :uuid]
                     :role         "owner"}]})

        ;; Create #general channel
        (db/execute! tx
          {:insert-into :channels
           :values [{:id           [:cast channel-id :uuid]
                     :community-id [:cast community-id :uuid]
                     :name         "general"
                     :type         "text"
                     :visibility   "public"}]})

        ;; Add creator to #general
        (db/execute! tx
          {:insert-into :channel-members
           :values [{:channel-id [:cast channel-id :uuid]
                     :user-id    [:cast user-id :uuid]}]}))

      ;; Fetch the created community
      (let [community (db/execute-one! ds
                        {:select [:*]
                         :from   [:communities]
                         :where  [:= :id [:cast community-id :uuid]]})]

        ;; Publish event
        (publish-event! nats
          (str "chat.events." community-id)
          :community/created
          {:community-id community-id
           :name         name-v
           :slug         slug
           :created-by   user-id})

        (mw/json-response 201 community)))))

(defn list-communities
  "GET /api/communities
   Returns communities the authenticated user belongs to."
  [request]
  (let [user-id (require-user! request)
        ds      (get-in request [:system :ds])
        communities (db/execute! ds
                      {:select [:c.*]
                       :from   [[:communities :c]]
                       :join   [[:community-members :cm]
                                [:= :c.id :cm.community-id]]
                       :where  [:= :cm.user-id [:cast user-id :uuid]]
                       :order-by [[:c.name :asc]]})]
    (mw/json-response communities)))

(defn get-community
  "GET /api/communities/:id
   Returns community details. Requires membership."
  [request]
  (let [user-id      (require-user! request)
        ds           (get-in request [:system :ds])
        community-id (get-in request [:path-params :id])
        _            (check-membership! ds community-id user-id)
        community    (db/execute-one! ds
                       {:select [:*]
                        :from   [:communities]
                        :where  [:= :id [:cast community-id :uuid]]})]
    (if community
      (mw/json-response community)
      (mw/error-response 404 "NOT_FOUND" "Community not found"))))

(defn update-community
  "PUT /api/communities/:id
   Updates community name/slug. Owner only."
  [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 :id])
        params       (:body-params request)]

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

    ;; Validate slug format if provided
    (when (and (:slug params) (not (re-matches #"[a-z0-9][a-z0-9-]*[a-z0-9]" (:slug params))))
      (throw (ex-info "Slug must be lowercase letters, digits, and hyphens"
                      {:type :ajet.chat/validation-error})))

    ;; Check slug uniqueness if changing
    (when (:slug params)
      (let [existing (db/execute-one! ds
                       {:select [:id]
                        :from   [:communities]
                        :where  [:and
                                 [:= :slug (:slug params)]
                                 [:!= :id [:cast community-id :uuid]]]})]
        (when existing
          (throw (ex-info "A community with this slug already exists"
                          {:type :ajet.chat/conflict})))))

    (let [updates (cond-> {}
                    (:name params) (assoc :name (:name params))
                    (:slug params) (assoc :slug (:slug params)))]
      (when (seq updates)
        (db/execute! ds
          {:update :communities
           :set    updates
           :where  [:= :id [:cast community-id :uuid]]}))

      (let [community (db/execute-one! ds
                        {:select [:*]
                         :from   [:communities]
                         :where  [:= :id [:cast community-id :uuid]]})]
        (publish-event! nats
          (str "chat.events." community-id)
          :community/updated
          {:community-id community-id
           :updated-by   user-id
           :changes      updates})

        (mw/json-response community)))))

(defn delete-community
  "DELETE /api/communities/:id
   Deletes community and all associated data. Owner only."
  [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 :id])]

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

    ;; CASCADE handles cleanup of members, channels, messages, etc.
    (db/execute! ds
      {:delete-from :communities
       :where       [:= :id [:cast community-id :uuid]]})

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

    (mw/json-response 204 nil)))
