/**
 * Issue #1533 — Phase A contract test: failure semantics.
 *
 * Rule 34: reads distinguish empty-dir from unreadable-dir — `{ok:false}` vs
 * `[]`. Where the current surface already does this, we pin it; where it
 * doesn't, we record the gap (for the silent-failures issue) rather than
 * change behavior here.
 *
 * Also pins containment: a FILE where a directory is expected is skipped via
 * lstat().isDirectory(), not existsSync() (rule 24).
 */

import assert from "node:assert/strict";
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import test from "node:test";

import { StorageManager } from "../storage.js";
import { makeStorage } from "./harness.js";

test("failure-semantics: readAllMemories on a fresh (empty) dir returns [] not null", async () => {
  const { storage, cleanup } = await makeStorage();
  try {
    const result = await storage.readAllMemories();
    assert.ok(Array.isArray(result), "readAllMemories must return an array");
    assert.equal(result.length, 0, "empty store must return an empty array");
  } finally {
    await cleanup();
  }
});

test("failure-semantics: getMemoryById on empty store returns null (not throw)", async () => {
  const { storage, cleanup } = await makeStorage();
  try {
    const result = await storage.getMemoryById("anything");
    assert.equal(result, null);
  } finally {
    await cleanup();
  }
});

test("failure-semantics: readMemoryByPath on a non-existent file returns null (not throw)", async () => {
  const { storage, baseDir, cleanup } = await makeStorage();
  try {
    const result = await storage.readMemoryByPath(path.join(baseDir, "facts", "2026-01-01", "nope.md"));
    assert.equal(result, null);
  } finally {
    await cleanup();
  }
});

test("failure-semantics: readMemoryByPath on a malformed markdown file returns null", async () => {
  const { storage, baseDir, cleanup } = await makeStorage();
  try {
    const dir = path.join(baseDir, "facts", "2026-01-01");
    await mkdir(dir, { recursive: true });
    const filePath = path.join(dir, "garbage.md");
    await writeFile(filePath, "this has no frontmatter at all\njust random text", "utf-8");

    const result = await storage.readMemoryByPath(filePath);
    assert.equal(result, null, "malformed file (no frontmatter, not entity) must return null");
  } finally {
    await cleanup();
  }
});

test("failure-semantics: a FILE where a category directory is expected does not crash readAllMemories", async () => {
  // Create a FILE named "facts" (not a directory) at the base dir, then scan.
  // collectActiveMemoryPaths uses lstat().isDirectory() (not existsSync) so a
  // file-as-directory entry is skipped without crashing (rule 24 containment).
  // We do NOT call ensureDirectories — it would try to mkdir facts/<today> and
  // fail on the file. The scan path must be robust independently of dir creation.
  const baseDir = await mkdtemp(path.join(os.tmpdir(), "remnic-fail-file-as-dir-"));
  try {
    StorageManager.clearAllStaticCaches();
    await writeFile(path.join(baseDir, "facts"), "I am a file not a directory", "utf-8");
    const storage = new StorageManager(baseDir);

    const result = await storage.readAllMemories();
    assert.ok(Array.isArray(result));
    assert.equal(result.length, 0, "a file-as-directory must not produce spurious memories");
  } finally {
    StorageManager.clearAllStaticCaches();
    await rm(baseDir, { recursive: true, force: true });
  }
});

test("failure-semantics: invalidateMemory on a missing memory returns false (not throw)", async () => {
  const { storage, cleanup } = await makeStorage();
  try {
    const result = await storage.invalidateMemory("totally-missing-id-99999");
    assert.equal(result, false);
  } finally {
    await cleanup();
  }
});

test("failure-semantics: ensureDirectories is idempotent (safe to call multiple times)", async () => {
  const { storage, cleanup } = await makeStorage();
  try {
    await storage.ensureDirectories();
    await storage.ensureDirectories();
    await storage.ensureDirectories();
  } finally {
    await cleanup();
  }
});
