(ns ajet.chat.shared.eventbus-integration-test
  (:require [clojure.test :refer [deftest testing is use-fixtures]]
            [ajet.chat.shared.eventbus :as eventbus]
            [ajet.chat.test-helpers :as th])
  (:import [java.util.concurrent CountDownLatch TimeUnit]))

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

;;; ---------------------------------------------------------------------------
;;; Connection tests
;;; ---------------------------------------------------------------------------

(deftest connected?-returns-true
  (is (true? (eventbus/connected? th/*nats*))))

;;; ---------------------------------------------------------------------------
;;; Pub/Sub tests
;;; ---------------------------------------------------------------------------

(deftest publish-and-subscribe
  (let [received (atom nil)
        latch    (CountDownLatch. 1)
        subject  (str "test.pubsub." (th/uuid))
        dispatcher (eventbus/subscribe! th/*nats* subject
                     (fn [event]
                       (reset! received event)
                       (.countDown latch)))]
    (try
      (eventbus/publish! th/*nats* subject :test/event {:msg "hello"})
      (is (.await latch 5 TimeUnit/SECONDS) "Timed out waiting for message")
      (is (= :test/event (keyword (:type @received))))
      (is (= "hello" (get-in @received [:payload :msg])))
      (is (some? (:id @received)))
      (is (some? (:ts @received)))
      (finally
        (eventbus/unsubscribe! th/*nats* dispatcher)))))

(deftest subscribe-wildcard
  (let [received (atom [])
        latch    (CountDownLatch. 2)
        base     (str "test.wild." (th/uuid))
        dispatcher (eventbus/subscribe! th/*nats* (str base ".*")
                     (fn [event]
                       (swap! received conj event)
                       (.countDown latch)))]
    (try
      (eventbus/publish! th/*nats* (str base ".a") :test/a {:n 1})
      (eventbus/publish! th/*nats* (str base ".b") :test/b {:n 2})
      (is (.await latch 5 TimeUnit/SECONDS) "Timed out waiting for messages")
      (is (= 2 (count @received)))
      (finally
        (eventbus/unsubscribe! th/*nats* dispatcher)))))

(deftest event-envelope-structure
  (let [received (atom nil)
        latch    (CountDownLatch. 1)
        subject  (str "test.envelope." (th/uuid))
        dispatcher (eventbus/subscribe! th/*nats* subject
                     (fn [event]
                       (reset! received event)
                       (.countDown latch)))]
    (try
      (eventbus/publish! th/*nats* subject :message/created {:text "hi"})
      (.await latch 5 TimeUnit/SECONDS)
      (let [evt @received]
        (is (string? (:id evt)))
        (is (string? (:ts evt)))
        (is (= "message/created" (name (:type evt))))
        (is (map? (:payload evt))))
      (finally
        (eventbus/unsubscribe! th/*nats* dispatcher)))))

(deftest publish-returns-event
  (let [event (eventbus/publish! th/*nats* (str "test.ret." (th/uuid)) :test/ret {:x 1})]
    (is (map? event))
    (is (= "test/ret" (:type event)))
    (is (some? (:id event)))))

;;; ---------------------------------------------------------------------------
;;; JetStream tests
;;; ---------------------------------------------------------------------------

(deftest ensure-stream-is-idempotent
  (testing "calling ensure-stream! twice doesn't throw"
    (is (nil? (eventbus/ensure-stream! th/*nats*)))
    (is (nil? (eventbus/ensure-stream! th/*nats*)))))

(deftest js-publish-and-subscribe
  (eventbus/ensure-stream! th/*nats*)
  (let [received (atom nil)
        latch    (CountDownLatch. 1)
        subject  (str "chat.events." (th/uuid))
        result   (eventbus/jetstream-subscribe! th/*nats* subject
                   (fn [event]
                     (reset! received event)
                     (.countDown latch))
                   {:durable-name (str "test-" (th/uuid))})]
    (try
      (eventbus/js-publish! th/*nats* subject :test/js {:data 42})
      (is (.await latch 10 TimeUnit/SECONDS) "Timed out waiting for JetStream message")
      (is (= "test/js" (name (:type @received))))
      (is (= 42 (get-in @received [:payload :data])))
      (finally
        (when-let [d (:dispatcher result)]
          (.closeDispatcher th/*nats* d))))))

(deftest js-publish-returns-event
  (eventbus/ensure-stream! th/*nats*)
  (let [event (eventbus/js-publish! th/*nats* (str "chat.events." (th/uuid))
                :test/js-ret {:x 1})]
    (is (map? event))
    (is (some? (:id event)))))
