/**
 * PR-C Deprecation Warning System — Tests
 * 
 * Run: node --test test/architecture/deprecation-warning.test.mjs
 */

import { describe, it, beforeEach } from "node:test";
import assert from "node:assert";
import { spawnSync } from "node:child_process";
import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import { warnOnce, resetWarnings, getWarningCount, warnMany, runDeprecationCheck } from "../../scripts/lib/deprecation-warning.mjs";

const CONFIG_PATH = join(homedir(), ".openclaw", "openclaw.json");
const TEST_DIR = join(homedir(), ".openclaw", "workspace", "test", "architecture");
const TMP_CONFIG = join(homedir(), ".openclaw", "openclaw-prc-test.json");

beforeEach(() => {
  resetWarnings();
});

describe("PR-C: Deprecation Warning System", () => {
  // ─── warnOnce utility tests ──────────────────────────────
  describe("warnOnce utility", () => {
    it("warn-01: same key only warns once", () => {
      const r1 = warnOnce("test.key1", "first message");
      const r2 = warnOnce("test.key1", "second message");
      const r3 = warnOnce("test.key1", "third message");
      
      assert.equal(r1, true, "first call must emit");
      assert.equal(r2, false, "second call must be suppressed");
      assert.equal(r3, false, "third call must be suppressed");
      assert.equal(getWarningCount(), 1, "only 1 unique key warned");
    });

    it("warn-02: different keys both warn", () => {
      const r1 = warnOnce("test.key1", "msg1");
      const r2 = warnOnce("test.key2", "msg2");
      
      assert.equal(r1, true);
      assert.equal(r2, true);
      assert.equal(getWarningCount(), 2);
    });

    it("warn-03: force option bypasses deduplication", () => {
      warnOnce("test.force", "first");
      const r = warnOnce("test.force", "second", { force: true });
      
      assert.equal(r, true, "force must emit even if already warned");
      assert.equal(getWarningCount(), 1, "key count unchanged (same key)");
    });

    it("warn-04: resetWarnings clears all state", () => {
      warnOnce("test.a", "a");
      warnOnce("test.b", "b");
      assert.equal(getWarningCount(), 2);
      
      resetWarnings();
      assert.equal(getWarningCount(), 0);
      
      // Can warn again after reset
      const r = warnOnce("test.a", "a again");
      assert.equal(r, true);
      assert.equal(getWarningCount(), 1);
    });

    it("warn-05: warnMany deduplicates across batch", () => {
      const emitted = warnMany([
        { key: "a", message: "msg a" },
        { key: "b", message: "msg b" },
        { key: "a", message: "msg a again" }, // duplicate
      ]);
      
      assert.equal(emitted.length, 2, "only 2 unique warnings emitted");
      assert.equal(getWarningCount(), 2);
    });

    it("warn-06: runDeprecationCheck catches errors", () => {
      const result = runDeprecationCheck("test-check", () => {
        return [{ key: "test.ok", message: "all good" }];
      });
      assert.equal(result.name, "test-check");
      assert.equal(result.warnings.length, 1);
      
      const errResult = runDeprecationCheck("bad-check", () => {
        throw new Error("simulated failure");
      });
      assert.equal(errResult.warnings[0].key, "bad-check");
      assert.ok(errResult.warnings[0].message.includes("simulated failure"));
    });
  });

  // ─── Config detection tests ─────────────────────────────
  describe("config detection", () => {
    it("detect-01: real config loads without crashing", () => {
      assert.ok(existsSync(CONFIG_PATH), "openclaw.json must exist");
      const config = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
      assert.ok(typeof config === "object");
      assert.ok(config.gateway || config.agents, "config must have expected sections");
    });

    it("detect-02: active-memory blocking mode detected", () => {
      // Read the actual config to check current state
      const config = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
      const activeCfg = config?.plugins?.entries?.["active-memory"];
      
      if (activeCfg) {
        const mode = activeCfg.config?.mode;
        console.log(`  [DETECT] active-memory mode: "${mode || "default"}"`);
        
        if (mode === "blocking") {
          const r = warnOnce("active-memory.blocking",
            "active-memory blocking mode is deprecated. Set mode to 'precompute'.");
          assert.equal(r, true);
        }
      }
      assert.ok(true, "check completed without crash");
    });

    it("detect-03: dreaming REM phase detected", () => {
      const config = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
      const memoryCoreCfg = config?.plugins?.entries?.["memory-core"];
      
      if (memoryCoreCfg?.config?.dreaming) {
        const dreaming = memoryCoreCfg.config.dreaming;
        const remEnabled = dreaming?.phases?.rem?.enabled;
        console.log(`  [DETECT] dreaming REM enabled: ${remEnabled ?? "not configured"}`);
        
        if (remEnabled === true) {
          warnOnce("dreaming.rem-phase",
            "Dreaming REM phase is deprecated. Will be replaced by Collect/Promote.");
        }
      }
      assert.ok(true, "check completed without crash");
    });

    it("detect-04: memory-wiki plugin reference detectable", () => {
      const wikiPath = join(
        "/opt/homebrew/lib/node_modules/openclaw",
        "dist/extensions/memory-wiki/openclaw.plugin.json"
      );
      const exists = existsSync(wikiPath);
      console.log(`  [DETECT] memory-wiki plugin: ${exists ? "present" : "not found"}`);
      
      if (exists) {
        const plugin = JSON.parse(readFileSync(wikiPath, "utf8"));
        assert.equal(plugin.id, "memory-wiki");
        warnOnce("memory-wiki.plugin",
          "memory-wiki plugin is legacy. Tools remain available during migration.");
      }
      assert.ok(true, "check completed without crash");
    });

    it("detect-05: QMD reference in config detectable", () => {
      const config = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
      const qmdRefs = [];
      
      // Check memory search
      if (config?.agents?.defaults?.memorySearch?.provider === "qmd") {
        qmdRefs.push("memorySearch.provider=qmd");
      }
      if (config?.agents?.defaults?.memorySearch?.backend === "qmd") {
        qmdRefs.push("memorySearch.backend=qmd");
      }
      
      // Check active-memory
      const activeQmd = config?.plugins?.entries?.["active-memory"]?.config?.qmd;
      if (activeQmd?.searchMode) {
        qmdRefs.push("active-memory.qmd.searchMode");
      }
      
      console.log(`  [DETECT] QMD references found: ${qmdRefs.length}`);
      for (const ref of qmdRefs) {
        console.log(`    • ${ref}`);
        warnOnce(`qmd.${ref}`, "QMD memory engine is legacy. Migrate to Builtin MemoryCore.");
      }
      
      assert.ok(true, "check completed without crash");
    });

    it("detect-06: Honcho/LanceDB plugins detectable", () => {
      const extDir = "/opt/homebrew/lib/node_modules/openclaw/dist/extensions";
      const legacyPlugins = ["memory-honcho", "memory-lancedb"];
      let found = 0;
      
      for (const name of legacyPlugins) {
        // Check if directory or plugin reference exists
        // These are NOT in the current install, which is expected (they're optional)
        const config = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
        const enabled = config?.plugins?.entries?.[name]?.enabled;
        console.log(`  [DETECT] ${name}: ${enabled === true ? "enabled" : "not enabled"}`);
        
        if (enabled === true) {
          warnOnce(`${name}.plugin`, `${name} memory plugin is legacy. Data readable during migration.`);
          found++;
        }
      }
      
      console.log(`  [DETECT] legacy memory plugins enabled: ${found}`);
      assert.ok(true, "check completed without crash");
    });

    it("detect-07: commitments auto-infer detectable", () => {
      const config = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
      const autoInfer = config?.commitments?.autoInfer;
      console.log(`  [DETECT] commitments auto-infer: ${autoInfer ?? "not configured"}`);
      
      if (autoInfer === true) {
        warnOnce("commitments.auto-infer",
          "Commitments auto-infer is deprecated. Use explicit cron tasks.");
      }
      assert.ok(true, "check completed without crash");
    });
  });

  // ─── Integration tests ──────────────────────────────────
  describe("integration", () => {
    it("integ-01: check-deprecations script runs without error", () => {
      const scriptPath = join(import.meta.dirname, "../../scripts/check-deprecations.mjs");
      const result = spawnSync("node", [scriptPath], {
        encoding: "utf8",
        timeout: 15000,
        maxBuffer: 1024 * 1024,
      });
      
      console.log("  [INTEG] check-deprecations exit code:", result.status);
      console.log("  [INTEG] check-deprecations stdout:", 
        result.stdout?.slice(0, 400) || "(empty)");
      
      if (result.stderr) {
        console.log("  [INTEG] stderr:", result.stderr.slice(0, 200));
      }
      
      // Script should never crash — always exit 0
      assert.equal(result.status, 0, 
        "check-deprecations must exit 0 even with warnings");
    });

    it("integ-02: deprecation warnings do NOT cause process exit", () => {
      // warnOnce writes to console.warn — it should never throw
      assert.doesNotThrow(() => {
        warnOnce("test.integration", "This is a test deprecation warning");
      }, "warnOnce must never throw");
      
      assert.equal(getWarningCount(), 1);
    });

    it("integ-03: all 8 deprecation categories checkable", () => {
      const categories = [
        "active-memory.blocking",
        "dreaming.rem-phase", 
        "dreaming.dream-diary",
        "qmd.reference",
        "honcho.plugin",
        "lancedb.plugin",
        "memory-wiki.plugin",
        "commitments.auto-infer",
      ];
      
      // Each category should have a defined key pattern
      for (const cat of categories) {
        assert.ok(typeof cat === "string" && cat.length > 0,
          `category ${cat} must be a non-empty string`);
      }
      
      console.log(`  [INTEG] ${categories.length} deprecation categories defined`);
      assert.equal(categories.length, 8, "must cover all 8 deprecation categories");
    });
  });
});
