import test from "node:test";
import assert from "node:assert/strict";
import os from "node:os";
import path from "node:path";
import { access, mkdtemp, rm } from "node:fs/promises";
import { parseConfig } from "../src/config.js";
import { Orchestrator } from "../src/orchestrator.js";
import { StorageManager } from "../src/storage.js";


function buildConfig(
  memoryDir: string,
  workspaceDir: string,
  enabled: boolean,
  autoBackfill = true,
) {
  return parseConfig({
    openaiApiKey: "sk-test",
    memoryDir,
    workspaceDir,
    qmdEnabled: false,
    topicExtractionEnabled: false,
    summarizationEnabled: false,
    identityEnabled: false,
    entitySummaryEnabled: false,
    qmdTierMigrationEnabled: enabled,
    qmdTierDemotionMinAgeDays: 0,
    qmdTierDemotionValueThreshold: 1,
    qmdTierPromotionValueThreshold: 1,
    qmdTierAutoBackfillEnabled: autoBackfill,
  });
}

test("tier migration cycle demotes hot memory when enabled", async () => {
  StorageManager.clearAllStaticCaches();
  const memoryDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-engram-tier-orch-enabled-"));
  const workspaceDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-engram-tier-orch-enabled-workspace-"));
  try {
    const orchestrator = new Orchestrator(buildConfig(memoryDir, workspaceDir, true)) as any;
    orchestrator.qmd = {
      updateCollection: async () => {},
      embedCollection: async () => {},
    };
    const storage = orchestrator.storage;
    await storage.writeMemory("fact", "demote me", { source: "test" });

    await orchestrator.runTierMigrationCycle(storage, "extraction");

    const hot = await storage.readAllMemories();
    const cold = await new StorageManager(path.join(storage.dir, "cold")).readAllMemories();
    assert.equal(hot.length, 0);
    assert.equal(cold.length, 1);
  } finally {
    await rm(memoryDir, { recursive: true, force: true });
    await rm(workspaceDir, { recursive: true, force: true });
  }
});

test("tier migration cycle is no-op when disabled", async () => {
  const memoryDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-engram-tier-orch-disabled-"));
  const workspaceDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-engram-tier-orch-disabled-workspace-"));
  try {
    const orchestrator = new Orchestrator(buildConfig(memoryDir, workspaceDir, false)) as any;
    orchestrator.qmd = {
      updateCollection: async () => {},
      embedCollection: async () => {},
    };
    const storage = orchestrator.storage;
    await storage.writeMemory("fact", "stay hot", { source: "test" });

    await orchestrator.runTierMigrationCycle(storage, "extraction");

    const hot = await storage.readAllMemories();
    const cold = await new StorageManager(path.join(storage.dir, "cold")).readAllMemories();
    assert.equal(hot.length, 1);
    assert.equal(cold.length, 0);
  } finally {
    await rm(memoryDir, { recursive: true, force: true });
    await rm(workspaceDir, { recursive: true, force: true });
  }
});

test("disabled extraction tier migration skips status state writes on hot path", async () => {
  const memoryDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-engram-tier-orch-disabled-status-"));
  const workspaceDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-engram-tier-orch-disabled-status-workspace-"));
  try {
    const orchestrator = new Orchestrator(buildConfig(memoryDir, workspaceDir, false)) as any;
    orchestrator.qmd = {
      updateCollection: async () => {},
      embedCollection: async () => {},
    };
    const storage = orchestrator.storage;
    await storage.writeMemory("fact", "stay hot", { source: "test" });

    await orchestrator.runTierMigrationCycle(storage, "extraction");

    const statusPath = path.join(memoryDir, "state", "tier-migration-status.json");
    await assert.rejects(() => access(statusPath));
  } finally {
    await rm(memoryDir, { recursive: true, force: true });
    await rm(workspaceDir, { recursive: true, force: true });
  }
});

test("enabled extraction tier migration skips status writes when no memory changes", async () => {
  const memoryDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-engram-tier-orch-noop-status-"));
  const workspaceDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-engram-tier-orch-noop-status-workspace-"));
  try {
    const config = buildConfig(memoryDir, workspaceDir, true);
    (config as any).qmdTierDemotionMinAgeDays = 365000;
    const orchestrator = new Orchestrator(config) as any;
    orchestrator.qmd = {
      updateCollection: async () => {},
      embedCollection: async () => {},
    };
    const storage = orchestrator.storage;
    await storage.writeMemory("fact", "keep hot and unchanged", { source: "test" });

    const summary = await orchestrator.runTierMigrationCycle(storage, "extraction");
    assert.equal(summary.migrated, 0);

    const statusPath = path.join(memoryDir, "state", "tier-migration-status.json");
    await assert.rejects(() => access(statusPath));
  } finally {
    await rm(memoryDir, { recursive: true, force: true });
    await rm(workspaceDir, { recursive: true, force: true });
  }
});

test("dry-run migration does not throttle the next extraction cycle", async () => {
  const memoryDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-engram-tier-orch-dryrun-throttle-"));
  const workspaceDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-engram-tier-orch-dryrun-throttle-workspace-"));
  try {
    const orchestrator = new Orchestrator(buildConfig(memoryDir, workspaceDir, true, true)) as any;
    orchestrator.qmd = {
      updateCollection: async () => {},
      embedCollection: async () => {},
    };
    const storage = orchestrator.storage;
    await storage.writeMemory("fact", "migrate-after-dry-run", { source: "test" });

    const preview = await orchestrator.runTierMigrationCycle(storage, "manual", {
      dryRun: true,
      limitOverride: 1,
      force: true,
    });
    assert.equal(preview.dryRun, true);
    assert.equal(preview.migrated, 1);

    const applied = await orchestrator.runTierMigrationCycle(storage, "extraction", {
      limitOverride: 1,
    });
    assert.notEqual(applied.skipped, "min_interval");
    assert.equal(applied.migrated, 1);
  } finally {
    await rm(memoryDir, { recursive: true, force: true });
    await rm(workspaceDir, { recursive: true, force: true });
  }
});

test("tier migration cycle is bounded per maintenance pass", async () => {
  const memoryDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-engram-tier-orch-bounded-"));
  const workspaceDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-engram-tier-orch-bounded-workspace-"));
  try {
    const orchestrator = new Orchestrator(buildConfig(memoryDir, workspaceDir, true, true)) as any;
    orchestrator.qmd = {
      updateCollection: async () => {},
      embedCollection: async () => {},
    };
    const storage = orchestrator.storage;
    for (let i = 0; i < 260; i += 1) {
      await storage.writeMemory("fact", `memory-${i}`, { source: "test" });
    }

    await orchestrator.runTierMigrationCycle(storage, "maintenance");

    const hot = await storage.readAllMemories();
    const cold = await new StorageManager(path.join(storage.dir, "cold")).readAllMemories();
    assert.equal(cold.length > 0, true);
    assert.equal(cold.length <= 200, true);
    assert.equal(hot.length + cold.length, 260);
  } finally {
    await rm(memoryDir, { recursive: true, force: true });
    await rm(workspaceDir, { recursive: true, force: true });
  }
});

test("tier migration extraction scan prioritizes oldest hot memories for demotion", async () => {
  const memoryDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-engram-tier-orch-oldest-first-"));
  const workspaceDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-engram-tier-orch-oldest-first-workspace-"));
  try {
    const orchestrator = new Orchestrator(buildConfig(memoryDir, workspaceDir, true, true)) as any;
    orchestrator.qmd = {
      updateCollection: async () => {},
      embedCollection: async () => {},
    };
    const storage = orchestrator.storage;

    const ids: string[] = [];
    for (let i = 0; i < 60; i += 1) {
      ids.push(await storage.writeMemory("fact", `memory-${i}`, { source: "test" }));
    }

    const oldIds = ids.slice(0, 12);
    for (const id of oldIds) {
      await storage.updateMemoryFrontmatter(id, {
        updated: "2001-01-01T00:00:00.000Z",
      });
    }

    await orchestrator.runTierMigrationCycle(storage, "extraction");

    const cold = await new StorageManager(path.join(storage.dir, "cold")).readAllMemories();
    const movedIds = new Set(cold.map((m) => m.frontmatter.id));
    assert.equal(oldIds.some((id) => movedIds.has(id)), true);
  } finally {
    await rm(memoryDir, { recursive: true, force: true });
    await rm(workspaceDir, { recursive: true, force: true });
  }
});

test("consolidation refreshes corpus after maintenance migration before archival", async () => {
  const memoryDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-engram-tier-orch-refresh-corpus-"));
  const workspaceDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-engram-tier-orch-refresh-corpus-workspace-"));
  try {
    const config = buildConfig(memoryDir, workspaceDir, true, true);
    (config as any).factArchivalEnabled = true;
    (config as any).identityEnabled = false;
    (config as any).summarizationEnabled = false;
    (config as any).topicExtractionEnabled = false;
    (config as any).entitySummaryEnabled = false;
    const orchestrator = new Orchestrator(config) as any;
    const storage = orchestrator.storage;
    const movedId = await storage.writeMemory("fact", "migrate-before-archival", { source: "test" });
    for (let i = 0; i < 4; i += 1) {
      await storage.writeMemory("fact", `filler-${i}`, { source: "test" });
    }

    orchestrator.extraction = {
      consolidate: async () => ({ items: [], profileUpdates: [], entityUpdates: [] }),
    };
    orchestrator.qmd = {
      updateCollection: async () => {},
      embedCollection: async () => {},
    };

    let archivalSawMoved = false;
    orchestrator.runFactArchival = async (memories: Array<{ frontmatter: { id: string } }>) => {
      archivalSawMoved = memories.some((m) => m.frontmatter.id === movedId);
      return 0;
    };

    await orchestrator.runConsolidationNow();
    assert.equal(archivalSawMoved, false);
  } finally {
    await rm(memoryDir, { recursive: true, force: true });
    await rm(workspaceDir, { recursive: true, force: true });
  }
});
