(ns ajet.chat.api.handlers.search
  "Full-text search handler using PostgreSQL tsvector.

   Supports searching messages, channels, and users with various filters:
   community, channel, author, date range, and cursor-based pagination."
  (:require [clojure.string :as str]
            [ajet.chat.shared.db :as db]
            [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- search-messages
  "Search messages using PostgreSQL full-text search with tsvector.
   Only returns messages in channels the user has access to."
  [ds user-id query opts]
  (let [{:keys [community-id channel-id from after before cursor limit]} opts
        ;; Build SQL with tsvector search
        base-sql (str
                   "SELECT m.id, m.channel_id, m.user_id, m.body_md, m.created_at, "
                   "u.username, u.display_name, c.name as channel_name "
                   "FROM messages m "
                   "JOIN channels c ON c.id = m.channel_id "
                   "JOIN users u ON u.id = m.user_id "
                   "JOIN channel_members cm ON cm.channel_id = m.channel_id AND cm.user_id = ?::uuid "
                   "WHERE to_tsvector('english', m.body_md) @@ plainto_tsquery('english', ?) ")
        params   (atom [user-id query])
        clauses  (atom [])]

    (when community-id
      (swap! clauses conj "AND c.community_id = ?::uuid")
      (swap! params conj community-id))

    (when channel-id
      (swap! clauses conj "AND m.channel_id = ?::uuid")
      (swap! params conj channel-id))

    (when from
      (swap! clauses conj "AND m.user_id = ?::uuid")
      (swap! params conj from))

    (when after
      (swap! clauses conj "AND m.created_at > ?::timestamptz")
      (swap! params conj after))

    (when before
      (swap! clauses conj "AND m.created_at < ?::timestamptz")
      (swap! params conj before))

    (when cursor
      (swap! clauses conj "AND m.created_at < (SELECT created_at FROM messages WHERE id = ?::uuid)")
      (swap! params conj cursor))

    (let [full-sql (str base-sql
                     (str/join " " @clauses)
                     " ORDER BY m.created_at DESC LIMIT ?")
          _ (swap! params conj limit)]
      (db/execute-sql! ds (into [full-sql] @params)))))

(defn- search-channels
  "Search channels by name substring within user's communities."
  [ds user-id query opts]
  (let [{:keys [community-id limit]} opts
        base-where [:and
                    [:like [:lower :c.name] [:lower (str "%" query "%")]]
                    [:in :c.community-id
                     {:select [:community-id]
                      :from   [:community-members]
                      :where  [:= :user-id [:cast user-id :uuid]]}]]
        where-clause (if community-id
                       [:and base-where
                        [:= :c.community-id [:cast community-id :uuid]]]
                       base-where)]
    (db/execute! ds
      {:select   [:c.*]
       :from     [[:channels :c]]
       :where    where-clause
       :order-by [[:c.name :asc]]
       :limit    limit})))

(defn- search-users
  "Search users by username or display_name substring."
  [ds _user-id query opts]
  (let [{:keys [limit]} opts]
    (db/execute! ds
      {:select   [:id :username :display-name :avatar-url :status-text]
       :from     [:users]
       :where    [:or
                  [:like [:lower :username] [:lower (str "%" query "%")]]
                  [:like [:lower :display-name] [:lower (str "%" query "%")]]]
       :order-by [[:username :asc]]
       :limit    limit})))

;;; ---------------------------------------------------------------------------
;;; Handler
;;; ---------------------------------------------------------------------------

(defn search-handler
  "GET /api/search?q=term&type=messages|channels|users&community_id=...&channel_id=...&from=...&after=...&before=...&cursor=...&limit=20"
  [request]
  (let [user-id (require-user! request)
        ds      (get-in request [:system :ds])
        params  (:query-params request)
        query   (get params "q")
        type    (get params "type")
        limit   (min (or (some-> (get params "limit") parse-long) 20) 100)
        opts    {:community-id (get params "community_id")
                 :channel-id   (get params "channel_id")
                 :from         (get params "from")
                 :after        (get params "after")
                 :before       (get params "before")
                 :cursor       (get params "cursor")
                 :limit        limit}]

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

    (let [result (case type
                   "messages" {:messages (search-messages ds user-id query opts)}
                   "channels" {:channels (search-channels ds user-id query opts)}
                   "users"    {:users    (search-users ds user-id query opts)}
                   ;; Default: search all types
                   {:messages (search-messages ds user-id query opts)
                    :channels (search-channels ds user-id query opts)
                    :users    (search-users ds user-id query opts)})]
      (mw/json-response result))))
