(ns lazygitclj.core-test
  "Unit tests for lazygitclj.core namespace - model and update functions"
  (:require [clojure.test :refer [deftest testing is]]
            [lazygitclj.core :as core]))

;; Helper to create key messages in the format the TUI library uses
(defn key-msg [k]
  (cond
    (char? k) [:key {:char k}]
    (keyword? k) [:key k]
    (and (vector? k) (= :ctrl (first k))) [:key {:ctrl true :char (second k)}]
    :else [:key k]))

;; === Model Tests ===

(deftest test-file-items
  (testing "combines staged and unstaged files"
    (let [model {:staged [{:path "a.txt"} {:path "b.txt"}]
                 :unstaged [{:path "c.txt"}]}
          items (core/file-items model)]
      (is (= 3 (count items)))
      (is (= :staged (:type (first items))))
      (is (= :unstaged (:type (last items))))))

  (testing "returns empty vector when no files"
    (let [model {:staged [] :unstaged []}]
      (is (= [] (core/file-items model))))))

(deftest test-current-items
  (testing "returns file items for files panel"
    (let [model {:panel :files
                 :staged [{:path "a.txt"}]
                 :unstaged [{:path "b.txt"}]}]
      (is (= 2 (count (core/current-items model))))))

  (testing "returns commits for commits panel"
    (let [model {:panel :commits
                 :commits-tab :commits
                 :commits [{:sha "abc123"}]
                 :reflog []}]
      (is (= 1 (count (core/current-items model))))))

  (testing "returns reflog for commits panel with reflog tab"
    (let [model {:panel :commits
                 :commits-tab :reflog
                 :commits []
                 :reflog [{:sha "def456"} {:sha "ghi789"}]}]
      (is (= 2 (count (core/current-items model))))))

  (testing "returns branches for branches panel"
    (let [model {:panel :branches
                 :branches-tab :local
                 :branches ["main" "feature"]}]
      (is (= 2 (count (core/current-items model))))))

  (testing "returns remote branches for branches panel with remotes tab"
    (let [model {:panel :branches
                 :branches-tab :remotes
                 :remote-branches ["origin/main"]}]
      (is (= 1 (count (core/current-items model))))))

  (testing "returns tags for branches panel with tags tab"
    (let [model {:panel :branches
                 :branches-tab :tags
                 :tags ["v1.0.0" "v2.0.0"]}]
      (is (= 2 (count (core/current-items model))))))

  (testing "returns stashes for stash panel"
    (let [model {:panel :stash
                 :stashes [{:ref "stash@{0}"}]}]
      (is (= 1 (count (core/current-items model)))))))

(deftest test-clamp-cursor
  (testing "clamps cursor to valid range"
    (let [model {:panel :files
                 :cursor 10
                 :staged [{:path "a.txt"}]
                 :unstaged []}
          clamped (core/clamp-cursor model)]
      (is (= 0 (:cursor clamped)))))

  (testing "keeps cursor at valid position"
    (let [model {:panel :files
                 :cursor 1
                 :staged [{:path "a.txt"} {:path "b.txt"}]
                 :unstaged []}
          clamped (core/clamp-cursor model)]
      (is (= 1 (:cursor clamped)))))

  (testing "handles empty lists"
    (let [model {:panel :files
                 :cursor 5
                 :staged []
                 :unstaged []}
          clamped (core/clamp-cursor model)]
      (is (= 0 (:cursor clamped))))))

(deftest test-truncate
  (testing "truncates long strings"
    (is (= "hello..." (core/truncate "hello world" 5))))

  (testing "keeps short strings"
    (is (= "hi" (core/truncate "hi" 10))))

  (testing "handles nil"
    (is (nil? (core/truncate nil 10)))))

;; === Update Tests ===

(deftest test-update-model-quit
  (testing "q returns quit command"
    (let [[model cmd] (core/update-model {} (key-msg \q))]
      (is (= [:quit] cmd))))

  (testing "ctrl-c returns quit command"
    (let [[model cmd] (core/update-model {} [:key {:ctrl true :char \c}])]
      (is (= [:quit] cmd)))))

(deftest test-update-model-panel-switch
  (testing "number keys switch panels (2-5 matching lazygit)"
    (let [[model _] (core/update-model {:panel :commits :cursor 0
                                        :staged [] :unstaged []} (key-msg \2))]
      (is (= :files (:panel model))))

    (let [[model _] (core/update-model {:panel :files :cursor 0
                                        :branches-tab :local
                                        :branches [] :remote-branches []
                                        :tags []} (key-msg \3))]
      (is (= :branches (:panel model))))

    (let [[model _] (core/update-model {:panel :files :cursor 0
                                        :commits-tab :commits
                                        :commits [] :reflog []} (key-msg \4))]
      (is (= :commits (:panel model))))

    (let [[model _] (core/update-model {:panel :files :cursor 0
                                        :stashes []} (key-msg \5))]
      (is (= :stash (:panel model)))))

  (testing "l key cycles panels right (files → branches → commits → stash → files)"
    (let [[model _] (core/update-model {:panel :files :cursor 0
                                        :branches-tab :local
                                        :branches [] :remote-branches []
                                        :tags []} (key-msg \l))]
      (is (= :branches (:panel model))))

    (let [[model _] (core/update-model {:panel :branches :cursor 0
                                        :commits-tab :commits
                                        :commits [] :reflog []} (key-msg \l))]
      (is (= :commits (:panel model))))

    (let [[model _] (core/update-model {:panel :commits :cursor 0
                                        :stashes []} (key-msg \l))]
      (is (= :stash (:panel model))))

    (let [[model _] (core/update-model {:panel :stash :cursor 0
                                        :staged [] :unstaged []} (key-msg \l))]
      (is (= :files (:panel model))))))

(deftest test-update-model-cursor-movement
  (testing "j moves cursor down"
    (let [[model _] (core/update-model {:panel :files :cursor 0
                                        :staged [{:path "a"} {:path "b"}]
                                        :unstaged []} (key-msg \j))]
      (is (= 1 (:cursor model)))))

  (testing "k moves cursor up"
    (let [[model _] (core/update-model {:panel :files :cursor 1
                                        :staged [{:path "a"} {:path "b"}]
                                        :unstaged []} (key-msg \k))]
      (is (= 0 (:cursor model)))))

  (testing "down arrow moves cursor down"
    (let [[model _] (core/update-model {:panel :files :cursor 0
                                        :staged [{:path "a"} {:path "b"}]
                                        :unstaged []} (key-msg :down))]
      (is (= 1 (:cursor model)))))

  (testing "up arrow moves cursor up"
    (let [[model _] (core/update-model {:panel :files :cursor 1
                                        :staged [{:path "a"} {:path "b"}]
                                        :unstaged []} (key-msg :up))]
      (is (= 0 (:cursor model))))))

(deftest test-update-model-help-menu
  (testing "? opens help menu"
    (let [[model _] (core/update-model {:menu-mode nil} (key-msg \?))]
      (is (= :help (:menu-mode model))))))

(deftest test-update-model-reset-menu
  (testing "D opens reset options menu"
    (let [[model _] (core/update-model {:menu-mode nil} (key-msg \D))]
      (is (= :reset-options (:menu-mode model))))))

(deftest test-update-model-input-mode
  (testing "c in files panel opens commit input when staged files exist"
    (let [[model _] (core/update-model {:panel :files
                                        :staged [{:path "a.txt"}]
                                        :unstaged []} (key-msg \c))]
      (is (= :commit (:input-mode model)))))

  (testing "c in files panel shows message when no staged files"
    (let [[model _] (core/update-model {:panel :files
                                        :staged []
                                        :unstaged []} (key-msg \c))]
      (is (= "Nothing staged to commit" (:message model))))))

(deftest test-update-input-mode
  (testing "escape cancels input mode"
    (let [[model _] (core/update-input-mode {:input-mode :commit
                                             :input-buffer "test"
                                             :input-context nil} (key-msg :escape))]
      (is (nil? (:input-mode model)))
      (is (= "" (:input-buffer model)))))

  (testing "backspace removes last character"
    (let [[model _] (core/update-input-mode {:input-mode :commit
                                             :input-buffer "abc"
                                             :input-context nil} (key-msg :backspace))]
      (is (= "ab" (:input-buffer model))))))

(deftest test-update-commits-tabs
  (testing "] switches to reflog tab in commits panel"
    (let [[model _] (core/update-model {:panel :commits
                                        :commits-tab :commits
                                        :cursor 0
                                        :commits []
                                        :reflog []} (key-msg \]))]
      (is (= :reflog (:commits-tab model)))))

  (testing "[ switches to commits tab in commits panel"
    (let [[model _] (core/update-model {:panel :commits
                                        :commits-tab :reflog
                                        :cursor 0
                                        :commits []
                                        :reflog []} (key-msg \[))]
      (is (= :commits (:commits-tab model))))))

(deftest test-update-branches-tabs
  (testing "] cycles branches tabs forward"
    (let [[model _] (core/update-branches {:branches-tab :local
                                           :cursor 0
                                           :branches []} (key-msg \]))]
      (is (= :remotes (:branches-tab model))))

    (let [[model _] (core/update-branches {:branches-tab :remotes
                                           :cursor 0
                                           :remote-branches []} (key-msg \]))]
      (is (= :tags (:branches-tab model))))

    (let [[model _] (core/update-branches {:branches-tab :tags
                                           :cursor 0
                                           :tags []} (key-msg \]))]
      (is (= :local (:branches-tab model)))))

  (testing "[ cycles branches tabs backward"
    (let [[model _] (core/update-branches {:branches-tab :local
                                           :cursor 0
                                           :tags []} (key-msg \[))]
      (is (= :tags (:branches-tab model))))))

(deftest test-update-stash-menu
  (testing "escape closes stash menu"
    (let [[model _] (core/update-stash-menu {:menu-mode :stash-options} (key-msg :escape))]
      (is (nil? (:menu-mode model))))))

(deftest test-update-reset-menu
  (testing "escape closes reset menu"
    (let [[model _] (core/update-reset-menu {:menu-mode :reset-options} (key-msg :escape))]
      (is (nil? (:menu-mode model))))))

(deftest test-update-help
  (testing "escape closes help"
    (let [[model _] (core/update-help {:menu-mode :help} (key-msg :escape))]
      (is (nil? (:menu-mode model)))))

  (testing "q closes help"
    (let [[model _] (core/update-help {:menu-mode :help} (key-msg \q))]
      (is (nil? (:menu-mode model)))))

  (testing "? closes help"
    (let [[model _] (core/update-help {:menu-mode :help} (key-msg \?))]
      (is (nil? (:menu-mode model))))))

;; Run tests when executed directly
(defn -main [& args]
  (clojure.test/run-tests 'lazygitclj.core-test))
