/**
 * Unit tests for SettingsDb — in-memory SQLite, no filesystem side effects.
 * We cannot import SettingsDb directly (singleton hits filesystem),
 * so we replicate the schema on an in-memory Database and exercise the SQL logic.
 */
import { describe, it, expect, beforeEach } from "bun:test";
import { Database } from "bun:sqlite";

// ── Schema (mirrors settings-db.ts) ─────────────────────────────────────────

const SCHEMA = `
CREATE TABLE IF NOT EXISTS schema_version (version INTEGER NOT NULL);
CREATE TABLE IF NOT EXISTS settings (key TEXT PRIMARY KEY, value TEXT NOT NULL);
CREATE TABLE IF NOT EXISTS projects (id TEXT PRIMARY KEY, config TEXT NOT NULL);
CREATE TABLE IF NOT EXISTS custom_themes (
  id TEXT PRIMARY KEY, name TEXT NOT NULL, palette TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS groups (
  id TEXT PRIMARY KEY, name TEXT NOT NULL, icon TEXT NOT NULL, position INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS keybindings (id TEXT PRIMARY KEY, chord TEXT NOT NULL);
`;

const SEED_GROUPS = `
INSERT OR IGNORE INTO groups VALUES ('dev',      'Development', 'wrench', 0);
INSERT OR IGNORE INTO groups VALUES ('test',     'Testing',     'flask',  1);
INSERT OR IGNORE INTO groups VALUES ('ops',      'DevOps',      'rocket', 2);
INSERT OR IGNORE INTO groups VALUES ('research', 'Research',    'scope',  3);
`;

function createDb(): Database {
  const db = new Database(":memory:");
  db.exec(SCHEMA);
  db.exec(SEED_GROUPS);
  return db;
}

// ── Helpers mirroring SettingsDb methods ─────────────────────────────────────

function getSetting(db: Database, key: string): string | null {
  const row = db.query<{ value: string }, [string]>(
    "SELECT value FROM settings WHERE key = ?"
  ).get(key);
  return row?.value ?? null;
}

function setSetting(db: Database, key: string, value: string): void {
  db.query(
    "INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value"
  ).run(key, value);
}

function getAll(db: Database): Record<string, string> {
  const rows = db.query<{ key: string; value: string }, []>(
    "SELECT key, value FROM settings"
  ).all();
  return Object.fromEntries(rows.map((r) => [r.key, r.value]));
}

// ── Tests ───────────────────────────────────────────────────────────────────

describe("SettingsDb", () => {
  let db: Database;

  beforeEach(() => {
    db = createDb();
  });

  // ── Settings CRUD ───────────────────────────────────────────────────────

  describe("getSetting / setSetting", () => {
    it("returns null for missing key", () => {
      expect(getSetting(db, "nonexistent")).toBeNull();
    });

    it("stores and retrieves a setting", () => {
      setSetting(db, "theme", "mocha");
      expect(getSetting(db, "theme")).toBe("mocha");
    });

    it("upserts on conflict", () => {
      setSetting(db, "theme", "mocha");
      setSetting(db, "theme", "latte");
      expect(getSetting(db, "theme")).toBe("latte");
    });
  });

  describe("getAll", () => {
    it("returns empty object when no settings exist", () => {
      expect(getAll(db)).toEqual({});
    });

    it("returns all key-value pairs", () => {
      setSetting(db, "a", "1");
      setSetting(db, "b", "2");
      expect(getAll(db)).toEqual({ a: "1", b: "2" });
    });
  });

  // ── Projects CRUD ──────────────────────────────────────────────────────

  describe("projects", () => {
    it("setProject + getProject round-trip", () => {
      const config = { id: "p1", name: "Test", cwd: "/tmp" };
      db.query(
        "INSERT INTO projects (id, config) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET config = excluded.config"
      ).run("p1", JSON.stringify(config));

      const row = db.query<{ config: string }, [string]>(
        "SELECT config FROM projects WHERE id = ?"
      ).get("p1");
      expect(JSON.parse(row!.config)).toEqual(config);
    });

    it("listProjects returns all", () => {
      db.query("INSERT INTO projects VALUES (?, ?)").run("a", JSON.stringify({ id: "a" }));
      db.query("INSERT INTO projects VALUES (?, ?)").run("b", JSON.stringify({ id: "b" }));
      const rows = db.query<{ config: string }, []>("SELECT config FROM projects").all();
      expect(rows).toHaveLength(2);
    });

    it("deleteProject removes entry", () => {
      db.query("INSERT INTO projects VALUES (?, ?)").run("x", JSON.stringify({ id: "x" }));
      db.query("DELETE FROM projects WHERE id = ?").run("x");
      const row = db.query<{ config: string }, [string]>(
        "SELECT config FROM projects WHERE id = ?"
      ).get("x");
      expect(row).toBeNull();
    });
  });

  // ── Groups CRUD ────────────────────────────────────────────────────────

  describe("groups", () => {
    it("seeds 4 default groups", () => {
      const rows = db.query<{ id: string }, []>(
        "SELECT id FROM groups ORDER BY position"
      ).all();
      expect(rows.map((r) => r.id)).toEqual(["dev", "test", "ops", "research"]);
    });

    it("createGroup upserts", () => {
      db.query(
        "INSERT INTO groups (id, name, icon, position) VALUES (?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET name = excluded.name"
      ).run("custom", "Custom", "star", 10);
      const row = db.query<{ name: string }, [string]>(
        "SELECT name FROM groups WHERE id = ?"
      ).get("custom");
      expect(row!.name).toBe("Custom");
    });

    it("deleteGroup removes entry", () => {
      db.query("DELETE FROM groups WHERE id = ?").run("ops");
      const rows = db.query<{ id: string }, []>("SELECT id FROM groups").all();
      expect(rows.map((r) => r.id)).not.toContain("ops");
    });
  });

  // ── Custom Themes CRUD ─────────────────────────────────────────────────

  describe("custom themes", () => {
    it("save and retrieve theme", () => {
      const palette = { base: "#1e1e2e", text: "#cdd6f4" };
      db.query(
        "INSERT INTO custom_themes (id, name, palette) VALUES (?, ?, ?) ON CONFLICT(id) DO UPDATE SET palette = excluded.palette"
      ).run("my-theme", "My Theme", JSON.stringify(palette));

      const row = db.query<{ palette: string }, [string]>(
        "SELECT palette FROM custom_themes WHERE id = ?"
      ).get("my-theme");
      expect(JSON.parse(row!.palette)).toEqual(palette);
    });

    it("deleteCustomTheme removes entry", () => {
      db.query("INSERT INTO custom_themes VALUES (?, ?, ?)").run("t1", "T1", "{}");
      db.query("DELETE FROM custom_themes WHERE id = ?").run("t1");
      const row = db.query<{ id: string }, [string]>(
        "SELECT id FROM custom_themes WHERE id = ?"
      ).get("t1");
      expect(row).toBeNull();
    });
  });

  // ── Keybindings CRUD ───────────────────────────────────────────────────

  describe("keybindings", () => {
    it("set and retrieve keybinding", () => {
      db.query(
        "INSERT INTO keybindings (id, chord) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET chord = excluded.chord"
      ).run("toggle-sidebar", "Ctrl+B");

      const rows = db.query<{ id: string; chord: string }, []>(
        "SELECT id, chord FROM keybindings"
      ).all();
      expect(Object.fromEntries(rows.map((r) => [r.id, r.chord]))).toEqual({
        "toggle-sidebar": "Ctrl+B",
      });
    });

    it("deleteKeybinding removes entry", () => {
      db.query("INSERT INTO keybindings VALUES (?, ?)").run("k1", "Ctrl+K");
      db.query("DELETE FROM keybindings WHERE id = ?").run("k1");
      const row = db.query<{ id: string }, [string]>(
        "SELECT id FROM keybindings WHERE id = ?"
      ).get("k1");
      expect(row).toBeNull();
    });
  });

  // ── Schema version tracking ────────────────────────────────────────────

  describe("schema_version", () => {
    it("starts empty and can be seeded", () => {
      const row = db.query<{ version: number }, []>(
        "SELECT version FROM schema_version LIMIT 1"
      ).get();
      expect(row).toBeNull(); // not yet inserted

      db.exec("INSERT INTO schema_version (version) VALUES (1)");
      const after = db.query<{ version: number }, []>(
        "SELECT version FROM schema_version LIMIT 1"
      ).get();
      expect(after!.version).toBe(1);
    });

    it("updates version", () => {
      db.exec("INSERT INTO schema_version (version) VALUES (1)");
      db.exec("UPDATE schema_version SET version = 2");
      const row = db.query<{ version: number }, []>(
        "SELECT version FROM schema_version LIMIT 1"
      ).get();
      expect(row!.version).toBe(2);
    });
  });
});
