(ns ajet.chat.api.messages-integration-test
  (:require [clojure.test :refer [deftest testing is use-fixtures]]
            [ajet.chat.api.handlers.messages :as messages]
            [ajet.chat.shared.db :as db]
            [ajet.chat.test-helpers :as th]))

(use-fixtures :once th/with-db-suite th/with-nats-suite)
(use-fixtures :each th/with-clean-db)

(defn- setup-channel
  "Create user + community + channel + channel membership. Returns [user comm channel]."
  []
  (let [user (th/create-user!)
        comm (th/create-community!)
        _    (th/add-member! (:id comm) (:id user) {:role "owner"})
        ch   (th/create-channel! (:id comm) {:name "general"})
        _    (th/join-channel! (:id ch) (:id user))]
    [user comm ch]))

;;; ---------------------------------------------------------------------------
;;; Send message
;;; ---------------------------------------------------------------------------

(deftest send-message-success
  (let [[user _ ch] (setup-channel)
        req  (th/mock-request {:method      :post
                               :user-id     (str (:id user))
                               :system      (th/test-system)
                               :path-params {:id (str (:id ch))}
                               :body-params {:body_md "hello world"}})
        resp (messages/send-message req)
        body (th/response-body resp)]
    (is (= 201 (:status resp)))
    (is (= "hello world" (:body-md body)))
    (is (some? (:id body)))))

(deftest send-message-blank-body-returns-422
  (let [[user _ ch] (setup-channel)
        req  (th/mock-request {:method      :post
                               :user-id     (str (:id user))
                               :system      (th/test-system)
                               :path-params {:id (str (:id ch))}
                               :body-params {:body_md ""}})]
    (is (= 422 (:status (th/with-exception-handler messages/send-message req))))))

(deftest send-message-non-member-returns-403
  (let [[_ _ ch] (setup-channel)
        outsider (th/create-user!)
        req  (th/mock-request {:method      :post
                               :user-id     (str (:id outsider))
                               :system      (th/test-system)
                               :path-params {:id (str (:id ch))}
                               :body-params {:body_md "sneaky"}})]
    (is (= 403 (:status (th/with-exception-handler messages/send-message req))))))

;;; ---------------------------------------------------------------------------
;;; List messages
;;; ---------------------------------------------------------------------------

(deftest list-messages-success
  (let [[user _ ch] (setup-channel)
        _ (th/create-message! (:id ch) (:id user) {:body-md "msg1"})
        _ (th/create-message! (:id ch) (:id user) {:body-md "msg2"})
        req  (th/mock-request {:user-id      (str (:id user))
                               :system       (th/test-system)
                               :path-params  {:id (str (:id ch))}
                               :query-params {}})
        resp (messages/list-messages req)
        body (th/response-body resp)]
    (is (= 200 (:status resp)))
    (is (= 2 (count body)))))

(deftest list-messages-with-limit
  (let [[user _ ch] (setup-channel)]
    (dotimes [i 5]
      (th/create-message! (:id ch) (:id user) {:body-md (str "msg-" i)}))
    (let [req  (th/mock-request {:user-id      (str (:id user))
                                 :system       (th/test-system)
                                 :path-params  {:id (str (:id ch))}
                                 :query-params {"limit" "3"}})
          resp (messages/list-messages req)
          body (th/response-body resp)]
      (is (= 200 (:status resp)))
      (is (= 3 (count body))))))

;;; ---------------------------------------------------------------------------
;;; Get message
;;; ---------------------------------------------------------------------------

(deftest get-message-success
  (let [[user _ ch] (setup-channel)
        msg  (th/create-message! (:id ch) (:id user))
        req  (th/mock-request {:user-id     (str (:id user))
                               :system      (th/test-system)
                               :path-params {:id (str (:id msg))}})
        resp (messages/get-message req)]
    (is (= 200 (:status resp)))))

;;; ---------------------------------------------------------------------------
;;; Edit message
;;; ---------------------------------------------------------------------------

(deftest edit-message-author-success
  (let [[user _ ch] (setup-channel)
        msg  (th/create-message! (:id ch) (:id user) {:body-md "original"})
        req  (th/mock-request {:method      :put
                               :user-id     (str (:id user))
                               :system      (th/test-system)
                               :path-params {:id (str (:id msg))}
                               :body-params {:body_md "updated"}})
        resp (messages/edit-message req)
        body (th/response-body resp)]
    (is (= 200 (:status resp)))
    (is (= "updated" (:body-md body)))
    (is (some? (:edited-at body)))))

(deftest edit-message-non-author-returns-403
  (let [[user _ ch] (setup-channel)
        other (th/create-user!)
        _     (th/add-member! (:community-id (db/execute-one! th/*ds*
                                               {:select [:community-id] :from [:channels]
                                                :where [:= :id (:id ch)]}))
                              (:id other))
        _     (th/join-channel! (:id ch) (:id other))
        msg   (th/create-message! (:id ch) (:id user) {:body-md "mine"})
        req   (th/mock-request {:method      :put
                                :user-id     (str (:id other))
                                :system      (th/test-system)
                                :path-params {:id (str (:id msg))}
                                :body-params {:body_md "stolen"}})]
    (is (= 403 (:status (th/with-exception-handler messages/edit-message req))))))

(deftest edit-message-blank-body-returns-422
  (let [[user _ ch] (setup-channel)
        msg  (th/create-message! (:id ch) (:id user))
        req  (th/mock-request {:method      :put
                               :user-id     (str (:id user))
                               :system      (th/test-system)
                               :path-params {:id (str (:id msg))}
                               :body-params {:body_md ""}})]
    (is (= 422 (:status (th/with-exception-handler messages/edit-message req))))))

;;; ---------------------------------------------------------------------------
;;; Delete message
;;; ---------------------------------------------------------------------------

(deftest delete-message-author-success
  (let [[user _ ch] (setup-channel)
        msg  (th/create-message! (:id ch) (:id user))
        req  (th/mock-request {:method      :delete
                               :user-id     (str (:id user))
                               :system      (th/test-system)
                               :path-params {:id (str (:id msg))}})
        resp (messages/delete-message req)]
    (is (= 204 (:status resp)))))

(deftest delete-message-admin-success
  (let [[owner _ ch] (setup-channel)
        admin (th/create-user!)
        comm-id (:community-id (db/execute-one! th/*ds*
                                 {:select [:community-id] :from [:channels]
                                  :where [:= :id (:id ch)]}))
        _ (th/add-member! comm-id (:id admin) {:role "admin"})
        _ (th/join-channel! (:id ch) (:id admin))
        msg (th/create-message! (:id ch) (:id owner) {:body-md "delete me"})
        req (th/mock-request {:method      :delete
                              :user-id     (str (:id admin))
                              :system      (th/test-system)
                              :path-params {:id (str (:id msg))}})]
    (is (= 204 (:status (messages/delete-message req))))))

;;; ---------------------------------------------------------------------------
;;; Thread
;;; ---------------------------------------------------------------------------

(deftest send-thread-reply
  (let [[user _ ch] (setup-channel)
        parent (th/create-message! (:id ch) (:id user) {:body-md "parent"})
        req    (th/mock-request {:method      :post
                                 :user-id     (str (:id user))
                                 :system      (th/test-system)
                                 :path-params {:id (str (:id ch))}
                                 :body-params {:body_md "reply"
                                               :parent_id (str (:id parent))}})
        resp   (messages/send-message req)
        body   (th/response-body resp)]
    (is (= 201 (:status resp)))
    (is (= (str (:id parent)) (str (:parent-id body))))))

(deftest get-thread-returns-replies
  (let [[user _ ch] (setup-channel)
        parent (th/create-message! (:id ch) (:id user) {:body-md "root"})
        _      (th/create-message! (:id ch) (:id user) {:body-md "reply1" :parent-id (:id parent)})
        _      (th/create-message! (:id ch) (:id user) {:body-md "reply2" :parent-id (:id parent)})
        req    (th/mock-request {:user-id     (str (:id user))
                                 :system      (th/test-system)
                                 :path-params {:id (str (:id parent))}})
        resp   (messages/get-thread req)
        body   (th/response-body resp)]
    (is (= 200 (:status resp)))
    (is (= 2 (count (:replies body))))
    (is (= "root" (get-in body [:root :body-md])))))

;;; ---------------------------------------------------------------------------
;;; Pagination
;;; ---------------------------------------------------------------------------

(deftest list-messages-cursor-based-pagination
  (let [[user _ ch] (setup-channel)
        msgs (doall (for [i (range 5)]
               (do
                 (Thread/sleep 10) ;; ensure distinct timestamps
                 (th/create-message! (:id ch) (:id user) {:body-md (str "msg-" i)}))))
        ;; Get first page (latest 3)
        req1 (th/mock-request {:user-id      (str (:id user))
                               :system       (th/test-system)
                               :path-params  {:id (str (:id ch))}
                               :query-params {"limit" "3"}})
        resp1 (messages/list-messages req1)
        page1 (th/response-body resp1)
        ;; Get next page using cursor
        oldest-id (str (:id (first page1)))
        req2 (th/mock-request {:user-id      (str (:id user))
                               :system       (th/test-system)
                               :path-params  {:id (str (:id ch))}
                               :query-params {"limit" "3" "before" oldest-id}})
        resp2 (messages/list-messages req2)
        page2 (th/response-body resp2)]
    (is (= 3 (count page1)))
    (is (= 2 (count page2)))
    ;; No overlap between pages
    (let [ids1 (set (map :id page1))
          ids2 (set (map :id page2))]
      (is (empty? (clojure.set/intersection ids1 ids2))))))
