/**
 * Store helper-level unit tests (pure logic, no model/runtime dependency).
 */

import { describe, test, expect } from "vitest";
import {
  homedir,
  resolve,
  getDefaultDbPath,
  _resetProductionModeForTesting,
  getPwd,
  getRealPath,
  isVirtualPath,
  parseVirtualPath,
  normalizeVirtualPath,
  normalizeDocid,
  isDocid,
  handelize,
  cleanupOrphanedVectors,
  sanitizeFTS5Term,
} from "../src/store";

// =============================================================================
// Path Utilities
// =============================================================================

describe("Path Utilities", () => {
  test("homedir returns HOME environment variable", () => {
    expect(homedir()).toBe(process.env.HOME || "/tmp");
  });

  test("resolve handles absolute paths", () => {
    expect(resolve("/foo/bar")).toBe("/foo/bar");
    expect(resolve("/foo", "/bar")).toBe("/bar");
  });

  test("resolve handles relative paths", () => {
    const pwd = process.env.PWD || process.cwd();
    expect(resolve("foo")).toBe(`${pwd}/foo`);
    expect(resolve("foo", "bar")).toBe(`${pwd}/foo/bar`);
  });

  test("resolve normalizes . and ..", () => {
    expect(resolve("/foo/bar/./baz")).toBe("/foo/bar/baz");
    expect(resolve("/foo/bar/../baz")).toBe("/foo/baz");
    expect(resolve("/foo/bar/../../baz")).toBe("/baz");
  });

  test("getDefaultDbPath throws in test mode without INDEX_PATH", () => {
    const originalIndexPath = process.env.INDEX_PATH;
    delete process.env.INDEX_PATH;
    // Reset production mode in case another test file set it (bun runs all
    // files in a single process, so module state leaks between files).
    _resetProductionModeForTesting();

    expect(() => getDefaultDbPath()).toThrow("Database path not set");

    if (originalIndexPath) {
      process.env.INDEX_PATH = originalIndexPath;
    }
  });

  test("getDefaultDbPath uses INDEX_PATH when set", () => {
    const originalIndexPath = process.env.INDEX_PATH;
    process.env.INDEX_PATH = "/tmp/test-index.sqlite";

    expect(getDefaultDbPath()).toBe("/tmp/test-index.sqlite");
    expect(getDefaultDbPath("custom")).toBe("/tmp/test-index.sqlite");

    if (originalIndexPath) {
      process.env.INDEX_PATH = originalIndexPath;
    } else {
      delete process.env.INDEX_PATH;
    }
  });

  test("getPwd returns current working directory", () => {
    const pwd = getPwd();
    expect(pwd).toBeTruthy();
    expect(typeof pwd).toBe("string");
  });

  test("getRealPath resolves symlinks", () => {
    const result = getRealPath("/tmp");
    expect(result).toBeTruthy();
    expect(result === "/tmp" || result === "/private/tmp").toBe(true);
  });
});

// =============================================================================
// Handelize Tests
// =============================================================================

describe("cleanupOrphanedVectors", () => {
  test("returns 0 when vec table exists in schema but sqlite-vec is unavailable", () => {
    const prepare = (sql: string) => {
      if (sql.includes("sqlite_master") && sql.includes("vectors_vec")) {
        return { get: () => ({ name: "vectors_vec" }) };
      }
      if (sql.includes("SELECT 1 FROM vectors_vec LIMIT 0")) {
        return { get: () => { throw new Error("no such module: vec0"); } };
      }
      throw new Error(`Unexpected SQL in test: ${sql}`);
    };

    const db = {
      prepare,
      exec: () => {
        throw new Error("cleanup should not execute vector deletes when sqlite-vec is unavailable");
      },
    } as any;

    expect(cleanupOrphanedVectors(db)).toBe(0);
  });
});

// =============================================================================
// Handelize Tests
// =============================================================================

describe("handelize", () => {
  test("preserves original case", () => {
    expect(handelize("README.md")).toBe("README.md");
    expect(handelize("MyFile.MD")).toBe("MyFile.MD");
  });

  test("preserves folder structure", () => {
    expect(handelize("a/b/c/d.md")).toBe("a/b/c/d.md");
    expect(handelize("docs/api/README.md")).toBe("docs/api/README.md");
  });

  test("replaces non-word characters with dash", () => {
    expect(handelize("hello world.md")).toBe("hello-world.md");
    expect(handelize("file (1).md")).toBe("file-1.md");
    expect(handelize("foo@bar#baz.md")).toBe("foo-bar-baz.md");
  });

  test("collapses multiple special chars into single dash", () => {
    expect(handelize("hello   world.md")).toBe("hello-world.md");
    expect(handelize("foo---bar.md")).toBe("foo-bar.md");
    expect(handelize("a  -  b.md")).toBe("a-b.md");
  });

  test("removes leading and trailing dashes from segments", () => {
    expect(handelize("-hello-.md")).toBe("hello.md");
    expect(handelize("--test--.md")).toBe("test.md");
    expect(handelize("a/-b-/c.md")).toBe("a/b/c.md");
  });

  test("converts triple underscore to folder separator", () => {
    expect(handelize("foo___bar.md")).toBe("foo/bar.md");
    expect(handelize("notes___2025___january.md")).toBe("notes/2025/january.md");
    expect(handelize("a/b___c/d.md")).toBe("a/b/c/d.md");
  });

  test("handles complex real-world meeting notes", () => {
    const complexName = "Money Movement Licensing Review - 2025／11／19 10:25 EST - Notes by Gemini.md";
    const result = handelize(complexName);
    expect(result).toBe("Money-Movement-Licensing-Review-2025-11-19-10-25-EST-Notes-by-Gemini.md");
    expect(result).not.toContain(" ");
    expect(result).not.toContain("／");
    expect(result).not.toContain(":");
  });

  test("handles unicode characters", () => {
    expect(handelize("日本語.md")).toBe("日本語.md");
    expect(handelize("Зоны и проекты.md")).toBe("Зоны-и-проекты.md");
    expect(handelize("café-notes.md")).toBe("café-notes.md");
    expect(handelize("naïve.md")).toBe("naïve.md");
    expect(handelize("日本語-notes.md")).toBe("日本語-notes.md");
  });

  test("handles emoji filenames (issue #302)", () => {
    // Emoji-only filenames should convert to hex codepoints
    expect(handelize("🐘.md")).toBe("1f418.md");
    expect(handelize("🎉.md")).toBe("1f389.md");
    // Emoji mixed with text
    expect(handelize("notes 🐘.md")).toBe("notes-1f418.md");
    expect(handelize("🐘 elephant.md")).toBe("1f418-elephant.md");
    // Multiple emojis
    expect(handelize("🐘🎉.md")).toBe("1f418-1f389.md");
    // Emoji in directory names
    expect(handelize("🐘/notes.md")).toBe("1f418/notes.md");
  });

  test("handles dates and times in filenames", () => {
    expect(handelize("meeting-2025-01-15.md")).toBe("meeting-2025-01-15.md");
    expect(handelize("notes 2025/01/15.md")).toBe("notes-2025/01/15.md");
    expect(handelize("call_10:30_AM.md")).toBe("call-10-30-AM.md");
  });

  test("handles special project naming patterns", () => {
    expect(handelize("PROJECT_ABC_v2.0.md")).toBe("PROJECT-ABC-v2-0.md");
    expect(handelize("[WIP] Feature Request.md")).toBe("WIP-Feature-Request.md");
    expect(handelize("(DRAFT) Proposal v1.md")).toBe("DRAFT-Proposal-v1.md");
  });

  test("handles symbol-only route filenames", () => {
    expect(handelize("routes/api/auth/$.ts")).toBe("routes/api/auth/$.ts");
    expect(handelize("app/routes/$id.tsx")).toBe("app/routes/$id.tsx");
  });

  test("filters out empty segments", () => {
    expect(handelize("a//b/c.md")).toBe("a/b/c.md");
    expect(handelize("/a/b/")).toBe("a/b");
    expect(handelize("///test///")).toBe("test");
  });

  test("throws error for invalid inputs", () => {
    expect(() => handelize("" )).toThrow("path cannot be empty");
    expect(() => handelize("   ")).toThrow("path cannot be empty");
    expect(() => handelize(".md")).toThrow("no valid filename content");
    expect(() => handelize("...")).toThrow("no valid filename content");
    expect(() => handelize("___")).toThrow("no valid filename content");
  });

  test("handles minimal valid inputs", () => {
    expect(handelize("a")).toBe("a");
    expect(handelize("1")).toBe("1");
    expect(handelize("a.md")).toBe("a.md");
  });

  test("normalizes virtual paths", () => {
    expect(normalizeVirtualPath("qmd://docs/readme.md")).toBe("qmd://docs/readme.md");
    expect(normalizeVirtualPath("docs/readme.md")).toBe("docs/readme.md");
  });

  test("detects virtual paths", () => {
    expect(isVirtualPath("qmd://docs/readme.md")).toBe(true);
    expect(isVirtualPath("/tmp/file.md")).toBe(false);
  });

  test("parses virtual paths", () => {
    expect(parseVirtualPath("qmd://docs/readme.md")).toEqual({
      collectionName: "docs",
      path: "readme.md",
    });
  });

  test("normalizes docids", () => {
    expect(normalizeDocid("123456")).toBe("123456");
    expect(normalizeDocid("#123456")).toBe("123456");
  });

  test("checks docid validity", () => {
    expect(isDocid("123456")).toBe(true);
    expect(isDocid("#123456")).toBe(true);
    expect(isDocid("bad-id")).toBe(false);
    expect(isDocid("12345")).toBe(false);
  });
});

// =============================================================================
// sanitizeFTS5Term Tests
// =============================================================================

describe("sanitizeFTS5Term", () => {
  test("preserves underscores in snake_case identifiers", () => {
    expect(sanitizeFTS5Term("my_variable")).toBe("my_variable");
    expect(sanitizeFTS5Term("MAX_RETRIES")).toBe("max_retries");
    expect(sanitizeFTS5Term("__init__")).toBe("__init__");
  });

  test("preserves alphanumeric characters", () => {
    expect(sanitizeFTS5Term("hello123")).toBe("hello123");
    expect(sanitizeFTS5Term("test")).toBe("test");
  });

  test("preserves apostrophes for contractions", () => {
    expect(sanitizeFTS5Term("don't")).toBe("don't");
    expect(sanitizeFTS5Term("it's")).toBe("it's");
  });

  test("strips other punctuation", () => {
    expect(sanitizeFTS5Term("hello!")).toBe("hello");
    expect(sanitizeFTS5Term("test@value")).toBe("testvalue");
    expect(sanitizeFTS5Term("a.b")).toBe("ab");
  });

  test("lowercases output", () => {
    expect(sanitizeFTS5Term("Hello")).toBe("hello");
    expect(sanitizeFTS5Term("MY_VAR")).toBe("my_var");
  });

  test("handles unicode letters and numbers", () => {
    expect(sanitizeFTS5Term("café")).toBe("café");
    expect(sanitizeFTS5Term("日本語")).toBe("日本語");
  });
});
