(ns ajet.chat.shared.db
  "Database layer — HikariCP connection pool, HoneySQL query execution, Migratus migrations."
  (:require [next.jdbc :as jdbc]
            [next.jdbc.result-set :as rs]
            [honey.sql :as sql]
            [clojure.tools.logging :as log]
            [migratus.core :as migratus])
  (:import [com.zaxxer.hikari HikariDataSource HikariConfig]))

;;; ---------------------------------------------------------------------------
;;; Connection Pool
;;; ---------------------------------------------------------------------------

(defn make-datasource
  "Create a HikariCP connection pool from config map.

   Config shape:
     {:host \"localhost\" :port 5432 :dbname \"ajet_chat\"
      :user \"ajet\" :password \"...\" :pool-size 10}"
  [config]
  (let [{:keys [host port dbname user password pool-size]
         :or   {host "localhost" port 5432 dbname "ajet_chat"
                user "ajet" pool-size 10}} config
        jdbc-url (format "jdbc:postgresql://%s:%d/%s" host port dbname)
        hk-config (doto (HikariConfig.)
                    (.setJdbcUrl jdbc-url)
                    (.setUsername user)
                    (.setMaximumPoolSize pool-size)
                    (.setMinimumIdle (min 2 pool-size))
                    (.setConnectionTimeout 10000)
                    (.setIdleTimeout 600000)
                    (.setMaxLifetime 1800000)
                    (.setPoolName "ajet-chat-pool"))]
    (when password
      (.setPassword hk-config password))
    (HikariDataSource. hk-config)))

(defn close-datasource
  "Close a HikariCP datasource."
  [^HikariDataSource ds]
  (.close ds))

;;; ---------------------------------------------------------------------------
;;; Query Execution (HoneySQL)
;;; ---------------------------------------------------------------------------

(def ^:private default-opts
  {:builder-fn rs/as-unqualified-kebab-maps})

(def ^:private slow-query-threshold-ms 500)

(defn- log-slow-query [sql-vec duration-ms]
  (when (> duration-ms slow-query-threshold-ms)
    (log/warn (format "Slow query (%.0fms): %s" (double duration-ms) (first sql-vec)))))

(defn execute!
  "Execute a HoneySQL map, returning a vector of result maps."
  [ds hsql-map]
  (let [sql-vec  (sql/format hsql-map)
        start    (System/nanoTime)
        result   (jdbc/execute! ds sql-vec default-opts)
        duration (/ (- (System/nanoTime) start) 1e6)]
    (log-slow-query sql-vec duration)
    result))

(defn execute-one!
  "Execute a HoneySQL map, returning the first result map or nil."
  [ds hsql-map]
  (let [sql-vec  (sql/format hsql-map)
        start    (System/nanoTime)
        result   (jdbc/execute-one! ds sql-vec default-opts)
        duration (/ (- (System/nanoTime) start) 1e6)]
    (log-slow-query sql-vec duration)
    result))

(defn execute-sql!
  "Execute a raw SQL vector [sql & params], returning a vector of result maps."
  [ds sql-vec]
  (let [start    (System/nanoTime)
        result   (jdbc/execute! ds sql-vec default-opts)
        duration (/ (- (System/nanoTime) start) 1e6)]
    (log-slow-query sql-vec duration)
    result))

(defn execute-sql-one!
  "Execute a raw SQL vector, returning the first result map or nil."
  [ds sql-vec]
  (let [start    (System/nanoTime)
        result   (jdbc/execute-one! ds sql-vec default-opts)
        duration (/ (- (System/nanoTime) start) 1e6)]
    (log-slow-query sql-vec duration)
    result))

(defn plan
  "Return a reducible query plan from a HoneySQL map."
  [ds hsql-map]
  (jdbc/plan ds (sql/format hsql-map) default-opts))

;;; ---------------------------------------------------------------------------
;;; Transactions
;;; ---------------------------------------------------------------------------

(defmacro with-transaction
  "Execute body within a database transaction.

   Usage: (with-transaction [tx ds] (execute! tx ...))"
  [[tx ds & opts] & body]
  `(jdbc/with-transaction [~tx ~ds ~@opts]
     ~@body))

;;; ---------------------------------------------------------------------------
;;; Migrations (Migratus)
;;; ---------------------------------------------------------------------------

(defn- migratus-config
  "Build Migratus config from datasource and migration options."
  [ds {:keys [location] :or {location "migrations"}}]
  {:store         :database
   :migration-dir location
   :db            {:datasource ds}})

(defn migrate!
  "Run pending migrations forward."
  [ds & [opts]]
  (log/info "Running database migrations...")
  (migratus/migrate (migratus-config ds (or opts {})))
  (log/info "Migrations complete."))

(defn rollback!
  "Rollback the last migration."
  [ds & [opts]]
  (log/info "Rolling back last migration...")
  (migratus/rollback (migratus-config ds (or opts {}))))
