/**
 * PR-3: Get Shadow Migration Test
 * 
 * Run: node --test test/memory-core/get-shadow.test.mjs
 * 
 * Compares legacy memory_get (file system read) vs MemoryCore.get()
 * across test cases: valid files, missing files, path traversal, etc.
 */

import { describe, it, before } from "node:test";
import assert from "node:assert";
import { readFileSync, existsSync } from "node:fs";
import { join, resolve } from "node:path";
import { fileURLToPath } from "node:url";

const __dirname = join(fileURLToPath(import.meta.url), "..", "..", "..");
const ROOT = resolve(__dirname);

let createMemoryCore;
let mc;

before(async () => {
  const mod = await import(join(ROOT, "src", "memory-core", "memory-core-facade.mjs"));
  createMemoryCore = mod.createMemoryCore;
  mc = createMemoryCore(ROOT);
});

/**
 * Simulate legacy memory_get behavior: read a file from workspace.
 */
function legacyGet(relativePath) {
  try {
    const normalized = relativePath.replace(/^\/+/, "");
    if (normalized.includes("..")) {
      return { ok: false, error: { code: "PATH_TRAVERSAL", message: "Path traversal denied" } };
    }
    
    const fullPath = join(ROOT, normalized);
    
    if (!existsSync(fullPath)) {
      return { ok: false, error: { code: "NOT_FOUND", message: "File not found" } };
    }
    
    const content = readFileSync(fullPath, "utf8");
    return { ok: true, content };
  } catch (e) {
    return { ok: false, error: { code: "READ_ERROR", message: e.message } };
  }
}

/**
 * Compare legacy and facade get results.
 */
function compareGetResults(testCase, legacy, facade) {
  const checks = {
    okConsistent: legacy.ok === facade.ok,
    hasContentWhenOk: !legacy.ok || (typeof legacy.content === "string" && typeof facade.content === "string"),
    errorWhenNotOk: legacy.ok || (legacy.error !== undefined && facade.error !== undefined),
    noContentWhenNotOk: legacy.ok || (legacy.content === undefined && facade.content === undefined),
  };

  // For successful reads, content should match
  if (legacy.ok && facade.ok) {
    // Allow small differences (line ending normalization)
    const legacyTrimmed = (legacy.content || "").replace(/\r\n/g, "\n").trim();
    const facadeTrimmed = (facade.content || "").replace(/\r\n/g, "\n").trim();
    checks.contentMatches = legacyTrimmed === facadeTrimmed;
  } else {
    checks.contentMatches = true; // N/A when not ok
  }

  const allCompatible = Object.values(checks).every(Boolean);

  return {
    testCase,
    legacyOk: legacy.ok,
    facadeOk: facade.ok,
    allCompatible,
    checks,
    legacyError: legacy.error || null,
    facadeError: facade.error || null,
  };
}

describe("PR-3: Get Shadow Migration Test", () => {

  // ─── Test 1 ──────────────────────────────────────────
  it("MEMORY.md — both return ok:true with matching content", async () => {
    const legacy = legacyGet("MEMORY.md");
    const facade = await mc.get("MEMORY.md");
    const cmp = compareGetResults("MEMORY.md", legacy, facade);
    
    assert.equal(legacy.ok, true, "Legacy get should succeed");
    assert.equal(facade.ok, true, "Facade get should succeed");
    assert.ok(cmp.checks.contentMatches, "Content should match between legacy and facade");
  });

  // ─── Test 2 ──────────────────────────────────────────
  it("memory/vault.md — both return ok:true", async () => {
    const legacy = legacyGet("memory/vault.md");
    const facade = await mc.get("memory/vault.md");
    const cmp = compareGetResults("memory/vault.md", legacy, facade);
    
    assert.equal(legacy.ok, facade.ok, "ok consistency");
    if (legacy.ok) {
      assert.ok(cmp.checks.contentMatches, "Content should match");
    }
  });

  // ─── Test 3 ──────────────────────────────────────────
  it("memory/daily/ — reads daily files", async () => {
    const files = ["memory/daily/2026-06-04.md", "memory/daily/2026-06-03.md"];
    
    for (const file of files) {
      const legacy = legacyGet(file);
      const facade = await mc.get(file);
      const cmp = compareGetResults(file, legacy, facade);
      
      // Both should agree on existence
      assert.equal(legacy.ok, facade.ok,
        `${file}: ok mismatch (legacy=${legacy.ok}, facade=${facade.ok})`);
      if (legacy.ok && facade.ok) {
        assert.ok(cmp.checks.contentMatches, `${file}: content mismatch`);
      }
    }
  });

  // ─── Test 4 ──────────────────────────────────────────
  it("nonexistent file — both return ok:false", async () => {
    const testFile = "definitely-does-not-exist-99999-xyz.md";
    
    const legacy = legacyGet(testFile);
    const facade = await mc.get(testFile);
    const cmp = compareGetResults(testFile, legacy, facade);
    
    assert.equal(legacy.ok, false, "Legacy should fail for missing file");
    assert.equal(facade.ok, false, "Facade should fail for missing file");
    assert.ok(cmp.allCompatible, `Compatibility: ${JSON.stringify(cmp.checks)}`);
  });

  // ─── Test 5 ──────────────────────────────────────────
  it("path traversal blocked by both", async () => {
    const traversalPaths = [
      "../etc/passwd",
      "../../../root/.ssh/id_rsa",
      "..%2F..%2Fetc/passwd",
    ];
    
    for (const path of traversalPaths) {
      const legacy = legacyGet(path);
      const facade = await mc.get(path);
      
      assert.equal(legacy.ok, false, `Legacy should block "${path}"`);
      assert.equal(facade.ok, false, `Facade should block "${path}"`);
    }
  });

  // ─── Test 6 ──────────────────────────────────────────
  it("empty path — both reject", async () => {
    const legacy = legacyGet("");
    const facade = await mc.get("");
    
    // Both should fail for empty path
    assert.equal(legacy.ok, false, "Legacy should fail for empty path");
    assert.equal(facade.ok, false, "Facade should fail for empty path");
  });

  // ─── Test 7 ──────────────────────────────────────────
  it("core files all accessible via both", async () => {
    const coreFiles = ["SOUL.md", "USER.md", "AGENTS.md", "IDENTITY.md", "TOOLS.md", "HEARTBEAT.md"];
    
    for (const file of coreFiles) {
      if (!existsSync(join(ROOT, file))) continue;
      
      const legacy = legacyGet(file);
      const facade = await mc.get(file);
      const cmp = compareGetResults(file, legacy, facade);
      
      assert.equal(legacy.ok, facade.ok, `${file}: ok mismatch`);
      if (legacy.ok && facade.ok) {
        assert.ok(cmp.checks.contentMatches, `${file}: content mismatch`);
      }
    }
  });

  // ─── Test 8 ──────────────────────────────────────────
  it("does not modify any real files", async () => {
    // Take snapshots of key files
    const keyFiles = ["MEMORY.md", "SOUL.md", "memory/vault.md"].filter(f => existsSync(join(ROOT, f)));
    const snapshots = {};
    for (const f of keyFiles) {
      snapshots[f] = readFileSync(join(ROOT, f), "utf8");
    }
    
    // Run lots of get operations
    await mc.get("MEMORY.md");
    await mc.get("memory/vault.md");
    await mc.get("memory/daily/2026-06-04.md");
    await mc.get("nonexistent");
    await mc.get("../etc/passwd"); // should be blocked
    
    // Verify no changes
    for (const f of keyFiles) {
      const current = readFileSync(join(ROOT, f), "utf8");
      assert.equal(current, snapshots[f], `${f} must not be modified`);
    }
  });

  // ─── Test 9 ──────────────────────────────────────────
  it("all compatibility checks pass for standard files", async () => {
    const testCases = ["MEMORY.md", "memory/vault.md", "nonexistent-file-xxx"];
    
    for (const tc of testCases) {
      const legacy = legacyGet(tc);
      const facade = await mc.get(tc);
      const cmp = compareGetResults(tc, legacy, facade);
      
      assert.ok(cmp.allCompatible,
        `${tc}: compatibility failure — ${Object.entries(cmp.checks).filter(([,v]) => !v).map(([k]) => k).join(", ")}`);
    }
  });
});
