(ns ajet.chat.cli.config
  "Configuration management for the CLI client.

   Config directory: ~/.config/ajet-chat/
   Files:
     config.edn  — server URL, default community, preferences
     session.edn — session token + user info
     state.edn   — last community, last channel per community"
  (:require [clojure.edn :as edn]
            [clojure.java.io :as io]
            [clojure.string :as str])
  (:import [java.time Instant]))

;;; ---------------------------------------------------------------------------
;;; Paths
;;; ---------------------------------------------------------------------------

(def ^:private config-dir-path
  "Path to the config directory."
  (str (System/getProperty "user.home") "/.config/ajet-chat"))

(defn- config-file ^java.io.File [filename]
  (io/file config-dir-path filename))

;;; ---------------------------------------------------------------------------
;;; Directory management
;;; ---------------------------------------------------------------------------

(defn ensure-config-dir!
  "Create the config directory if it does not exist. Returns the path."
  []
  (let [dir (io/file config-dir-path)]
    (when-not (.exists dir)
      (.mkdirs dir))
    config-dir-path))

;;; ---------------------------------------------------------------------------
;;; Generic EDN read/write helpers
;;; ---------------------------------------------------------------------------

(defn- read-edn-file
  "Read an EDN file. Returns nil if the file does not exist or is empty."
  [filename]
  (let [f (config-file filename)]
    (when (.exists f)
      (let [content (slurp f)]
        (when-not (str/blank? content)
          (edn/read-string content))))))

(defn- write-edn-file!
  "Write data as EDN to a file. Creates the config directory if needed."
  [filename data]
  (ensure-config-dir!)
  (spit (config-file filename) (pr-str data)))

(defn- delete-file!
  "Delete a file if it exists."
  [filename]
  (let [f (config-file filename)]
    (when (.exists f)
      (.delete f))))

;;; ---------------------------------------------------------------------------
;;; Session management
;;; ---------------------------------------------------------------------------

(defn load-session
  "Load session.edn. Returns nil if missing or expired.

   Session shape:
     {:token      \"base64url-session-token\"
      :user-id    \"uuid\"
      :username   \"alice\"
      :expires-at \"2026-03-19T...\"}"
  []
  (when-let [session (read-edn-file "session.edn")]
    (let [expires-at (:expires-at session)]
      (if (and expires-at
               (try
                 (.isBefore (Instant/parse expires-at) (Instant/now))
                 (catch Exception _ false)))
        nil
        session))))

(defn save-session!
  "Write session data to session.edn.

   data should contain :token, :user-id, :username, and optionally :expires-at."
  [data]
  (write-edn-file! "session.edn" data))

(defn clear-session!
  "Delete session.edn, effectively logging out."
  []
  (delete-file! "session.edn"))

;;; ---------------------------------------------------------------------------
;;; Config management
;;; ---------------------------------------------------------------------------

(def ^:private default-config
  {:server-url "http://localhost:3000"
   :default-community nil
   :tui {:theme :dark
         :image-viewer :timg
         :mouse true
         :timestamps :relative
         :notifications :bell}})

(defn load-config
  "Load config.edn, merged with defaults. Returns defaults if file missing."
  []
  (let [file-config (read-edn-file "config.edn")]
    (if file-config
      (merge-with (fn [a b]
                    (if (and (map? a) (map? b))
                      (merge a b)
                      b))
                  default-config file-config)
      default-config)))

(defn save-config!
  "Write config data to config.edn."
  [data]
  (write-edn-file! "config.edn" data))

;;; ---------------------------------------------------------------------------
;;; State management
;;; ---------------------------------------------------------------------------

(defn load-state
  "Load state.edn (last community, last channel per community).

   State shape:
     {:last-community  \"uuid\"
      :last-channels   {\"community-uuid\" \"channel-uuid\"}}"
  []
  (or (read-edn-file "state.edn")
      {:last-community nil
       :last-channels {}}))

(defn save-state!
  "Write state data to state.edn."
  [data]
  (write-edn-file! "state.edn" data))

;;; ---------------------------------------------------------------------------
;;; Context builder
;;; ---------------------------------------------------------------------------

(defn make-ctx
  "Build an API client context map from session + config.

   Returns a ctx suitable for ajet.chat.shared.api-client functions:
     {:base-url   \"http://localhost:3000\"
      :auth-token \"base64url-token\"
      :user-id    \"uuid\"
      :trace-id   \"uuid\"}

   Throws ex-info with :type :ajet.chat/auth-error if no valid session."
  []
  (let [config  (load-config)
        session (load-session)]
    (when-not session
      (throw (ex-info "Not logged in"
                      {:type :ajet.chat/auth-error
                       :hint "Run 'ajet login' to sign in"})))
    {:base-url   (:server-url config)
     :auth-token (:token session)
     :user-id    (:user-id session)
     :trace-id   (str (java.util.UUID/randomUUID))}))

(defn get-server-url
  "Get the configured server URL."
  []
  (:server-url (load-config)))

(defn get-default-community
  "Get the default community slug from config, or the last-used community from state."
  []
  (or (:default-community (load-config))
      (:last-community (load-state))))
