(ns ajet.chat.e2e.setup-flow-e2e-test
  "E2E tests for the admin setup wizard flow.
   Requires: docker compose -f docker-compose.test.yml --profile e2e up -d

   Tests exercise the full first-deployment bootstrap:
     /setup (configure providers) -> OAuth login -> /setup (create community) -> /app

   Since E2E tests can't perform real OAuth, tests that need an authenticated
   admin create the user directly via DB and establish a session."
  (:require [clojure.test :refer [deftest testing is use-fixtures]]
            [clojure.string :as str]
            [clojure.data.json :as json]
            [babashka.http-client :as http]
            [ajet.chat.shared.db :as db]
            [ajet.chat.auth-gw.auth :as auth]
            [ajet.chat.test-helpers :as th]))

(def ^:private base-url (or (System/getenv "AJET_TEST_BASE_URL") "http://localhost:3100"))
(def ^:private no-redirect-client (http/client {:follow-redirects :never}))

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

;;; ---------------------------------------------------------------------------
;;; Helpers
;;; ---------------------------------------------------------------------------

(defn- parse-json [s]
  (when-not (str/blank? s)
    (json/read-str s :key-fn keyword)))

(defn- extract-set-cookie
  "Extract a specific cookie value from Set-Cookie response header(s)."
  [resp cookie-name]
  (let [raw    (get-in resp [:headers "set-cookie"])
        values (cond
                 (string? raw) [raw]
                 (sequential? raw) raw
                 :else [])]
    (some (fn [c]
            (when (str/starts-with? c (str cookie-name "="))
              (second (re-find (re-pattern (str cookie-name "=([^;]+)")) c))))
          values)))

(defn- setup-admin!
  "Create an admin user directly via DB and return a session cookie string.
   Used to skip OAuth flow in tests that need an authenticated admin."
  []
  (let [user    (th/create-user! {:username "admin" :display-name "Admin"})
        user-id (:id user)
        token   (auth/create-session! th/*ds* user-id 30)]
    {:user user :cookie (str "ajet_session=" token)}))

(defn- insert-test-provider!
  "Insert an OAuth provider directly via DB."
  [& [{:keys [provider-type slug display-name]
       :or   {provider-type "github" slug "github" display-name "GitHub"}}]]
  (db/execute-one! th/*ds*
    {:insert-into :oauth-providers
     :values [{:provider-type provider-type
               :display-name  display-name
               :slug          slug
               :client-id     "test-client-id"
               :client-secret "test-client-secret"}]
     :returning [:*]}))

(defn- mark-setup-complete! []
  (db/execute-sql! th/*ds*
    ["UPDATE system_settings SET value = 'true' WHERE key = 'setup_completed'"]))

;;; ---------------------------------------------------------------------------
;;; Setup Wizard: Provider Configuration
;;; ---------------------------------------------------------------------------

(deftest setup-page-shows-provider-config-when-no-providers
  (testing "/setup shows OAuth provider configuration form when no providers exist"
    (let [resp (http/get (str base-url "/setup")
                 {:throw false})]
      (is (= 200 (:status resp)))
      (is (re-find #"(?i)configure.*oauth|oauth.*provider" (:body resp)))
      (is (re-find #"/setup/providers" (:body resp))))))

(deftest setup-add-provider-inserts-and-redirects
  (testing "POST /setup/providers adds a provider and redirects back to /setup"
    (let [resp (http/post (str base-url "/setup/providers")
                 {:throw   false
                  :client  no-redirect-client
                  :headers {"Content-Type" "application/x-www-form-urlencoded"}
                  :body    "provider-type=github&display-name=GitHub&slug=github&client-id=abc&client-secret=def"})]
      (is (= 302 (:status resp)))
      (is (= "/setup" (get-in resp [:headers "location"])))
      ;; Verify provider was created in DB
      (let [providers (db/execute! th/*ds*
                        {:select [:*] :from [:oauth-providers]})]
        (is (= 1 (count providers)))
        (is (= "github" (:provider-type (first providers))))))))

(deftest setup-add-provider-validates-required-fields
  (testing "POST /setup/providers with missing fields shows error"
    (let [resp (http/post (str base-url "/setup/providers")
                 {:throw   false
                  :headers {"Content-Type" "application/x-www-form-urlencoded"}
                  :body    "provider-type=github&display-name=&slug=&client-id=&client-secret="})]
      (is (= 200 (:status resp)))
      (is (re-find #"(?i)required" (:body resp))))))

(deftest setup-delete-provider-removes-from-db
  (testing "POST /setup/providers/:id/delete removes the provider"
    (let [provider (insert-test-provider!)
          resp     (http/post (str base-url "/setup/providers/" (:id provider) "/delete")
                     {:throw  false
                      :client no-redirect-client})]
      (is (= 302 (:status resp)))
      (let [providers (db/execute! th/*ds*
                        {:select [:*] :from [:oauth-providers]})]
        (is (zero? (count providers)))))))

;;; ---------------------------------------------------------------------------
;;; Setup Wizard: Login Redirect
;;; ---------------------------------------------------------------------------

(deftest setup-redirects-to-login-when-providers-configured
  (testing "/setup redirects to /auth/login when providers exist but no users"
    (insert-test-provider!)
    (let [resp (http/get (str base-url "/setup")
                 {:throw  false
                  :client no-redirect-client})]
      (is (= 302 (:status resp)))
      (is (= "/auth/login" (get-in resp [:headers "location"]))))))

(deftest login-page-renders-with-configured-providers
  (testing "Login page shows OAuth buttons for configured providers"
    (insert-test-provider! {:provider-type "github" :slug "my-github" :display-name "My GitHub"})
    (let [resp (http/get (str base-url "/auth/login")
                 {:throw false})]
      (is (= 200 (:status resp)))
      (is (re-find #"My GitHub" (:body resp)))
      (is (re-find #"provider=my-github" (:body resp))))))

(deftest login-page-redirects-to-setup-when-no-providers-and-no-users
  (testing "Login page redirects to /setup when no providers and no users exist"
    (let [resp (http/get (str base-url "/auth/login")
                 {:throw  false
                  :client no-redirect-client})]
      (is (= 302 (:status resp)))
      (is (= "/setup" (get-in resp [:headers "location"]))))))

;;; ---------------------------------------------------------------------------
;;; Setup Wizard: Community Creation
;;; ---------------------------------------------------------------------------

(deftest setup-shows-community-form-when-user-authenticated
  (testing "/setup shows community creation form when admin is authenticated and setup incomplete"
    (insert-test-provider!)
    (let [{:keys [cookie]} (setup-admin!)]
      (let [resp (http/get (str base-url "/setup")
                   {:throw   false
                    :headers {"Cookie" cookie}})]
        (is (= 200 (:status resp)))
        (is (re-find #"(?i)create.*community" (:body resp)))))))

(deftest setup-create-community-completes-wizard
  (testing "POST /setup/create-community creates community and marks setup complete"
    (insert-test-provider!)
    (let [{:keys [cookie]} (setup-admin!)
          resp (http/post (str base-url "/setup/create-community")
                 {:throw   false
                  :client  no-redirect-client
                  :headers {"Cookie"       cookie
                            "Content-Type" "application/x-www-form-urlencoded"}
                  :body    "name=My+Team&slug=my-team"})]
      (is (= 302 (:status resp)))
      (is (= "/" (get-in resp [:headers "location"])))
      ;; Verify community was created
      (let [communities (db/execute! th/*ds*
                          {:select [:*] :from [:communities]})]
        (is (= 1 (count communities)))
        (is (= "my-team" (:slug (first communities)))))
      ;; Verify setup_completed = true
      (let [{:keys [value]} (db/execute-one! th/*ds*
                              {:select [:value]
                               :from   [:system-settings]
                               :where  [:= :key "setup_completed"]})]
        (is (= "true" value))))))

(deftest setup-create-community-validates-slug
  (testing "POST /setup/create-community rejects invalid slug"
    (insert-test-provider!)
    (let [{:keys [cookie]} (setup-admin!)
          resp (http/post (str base-url "/setup/create-community")
                 {:throw   false
                  :headers {"Cookie"       cookie
                            "Content-Type" "application/x-www-form-urlencoded"}
                  :body    "name=Bad+Slug&slug=INVALID"})]
      (is (= 200 (:status resp)))
      (is (re-find #"(?i)slug" (:body resp))))))

;;; ---------------------------------------------------------------------------
;;; Post-Setup Behavior
;;; ---------------------------------------------------------------------------

(deftest setup-not-shown-after-completion
  (testing "/setup proxies to Web SM after setup is complete (not wizard)"
    (insert-test-provider!)
    (let [{:keys [cookie]} (setup-admin!)]
      (mark-setup-complete!)
      ;; /setup should now proxy to Web SM, not render the wizard
      ;; The exact response depends on Web SM, but it shouldn't be a wizard page
      (let [resp (http/get (str base-url "/setup")
                   {:throw   false
                    :client  no-redirect-client
                    :headers {"Cookie" cookie}})]
        ;; Should either succeed (Web SM handles it) or redirect
        ;; but NOT show the wizard provider config form
        (is (not (re-find #"/setup/providers" (or (:body resp) ""))))))))

