import test from "node:test";
import assert from "node:assert/strict";
import { createHash, generateKeyPairSync, sign } from "node:crypto";
import { access, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { deletePcApplicationData } from "./application-data-reset.js";
import { createEntitlementStore } from "./entitlement-store.js";

test("PC reset deletes only Code Remote Manager runtime data and preserves Codex data and receipts", async () => {
  const root = await mkdtemp(join(tmpdir(), "crm-pc-reset-"));
  const dataDir = join(root, "data");
  const logsDir = join(root, "logs");
  try {
    await Promise.all([
      write(join(dataDir, "sessions.json"), "sessions"),
      write(join(dataDir, "attachments", "session-1", "image.png"), "image"),
      write(join(dataDir, "artifacts", "session-1", "preview.html"), "preview"),
      write(join(dataDir, "thread-preferences.json"), "thread preferences"),
      write(join(dataDir, "project-preferences.json"), "project preferences"),
      write(join(dataDir, "usage-history.json"), "usage history"),
      write(join(dataDir, "account-token-usage.json"), "token usage"),
      write(join(dataDir, "security", "device-auth.json"), "paired devices"),
      write(join(logsDir, "backend-2026-07-17.log"), "safe log"),
      write(join(root, "account-login-attempts", "attempt-1", "temporary"), "temporary")
    ]);
    await Promise.all([
      write(join(dataDir, "action-receipts.json"), "receipt-ledger-must-remain"),
      write(join(root, "config.local.json"), "configuration-must-remain"),
      write(join(root, "config-backups", "config.1.json"), "backup-must-remain"),
      write(join(root, "accounts", "account-1", "codex-home", "auth.json"), "codex-auth-must-remain"),
      write(join(root, "accounts", "account-1", "codex-home", "sessions", "chat.jsonl"), "codex-chat-must-remain")
    ]);

    const result = await deletePcApplicationData({ dataDir, logsDir });

    for (const path of [
      join(dataDir, "sessions.json"), join(dataDir, "attachments"), join(dataDir, "artifacts"),
      join(dataDir, "thread-preferences.json"), join(dataDir, "project-preferences.json"),
      join(dataDir, "usage-history.json"), join(dataDir, "account-token-usage.json"),
      join(dataDir, "security"), logsDir, join(root, "account-login-attempts")
    ]) await assert.rejects(access(path));
    assert.equal(await readFile(join(dataDir, "action-receipts.json"), "utf8"), "receipt-ledger-must-remain");
    assert.equal(await readFile(join(root, "config.local.json"), "utf8"), "configuration-must-remain");
    assert.equal(await readFile(join(root, "config-backups", "config.1.json"), "utf8"), "backup-must-remain");
    assert.equal(await readFile(join(root, "accounts", "account-1", "codex-home", "auth.json"), "utf8"), "codex-auth-must-remain");
    assert.equal(await readFile(join(root, "accounts", "account-1", "codex-home", "sessions", "chat.jsonl"), "utf8"), "codex-chat-must-remain");
    assert.deepEqual(result, { deleted: true, pendingCleanup: 0 });
  } finally {
    await rm(root, { recursive: true, force: true });
  }
});

test("PC reset rolls staged data back when it cannot safely stage every target", async () => {
  const root = await mkdtemp(join(tmpdir(), "crm-pc-reset-failure-"));
  const dataDir = join(root, "data");
  try {
    await write(join(dataDir, "sessions.json"), "sessions");
    await write(join(dataDir, "attachments", "session-1", "image.png"), "image");
    let renameCalls = 0;

    await assert.rejects(deletePcApplicationData({ dataDir, logsDir: join(root, "logs") }, {
      async rename(from, to) {
        renameCalls += 1;
        if (renameCalls === 2) throw Object.assign(new Error("locked"), { code: "EPERM" });
        const { rename } = await import("node:fs/promises");
        return rename(from, to);
      }
    }), /application_data_reset_failed/);

    assert.equal(await readFile(join(dataDir, "sessions.json"), "utf8"), "sessions");
    assert.equal(await readFile(join(dataDir, "attachments", "session-1", "image.png"), "utf8"), "image");
  } finally {
    await rm(root, { recursive: true, force: true });
  }
});

test("PC reset removes entitlement state so a later restore binds to a new installation key", async () => {
  const root = await mkdtemp(join(tmpdir(), "crm-pc-reset-entitlement-"));
  const dataDir = join(root, "data");
  const signingKeys = generateKeyPairSync("ed25519");
  const publicKeys = { "release-2026-01": signingKeys.publicKey };
  const now = () => Date.UTC(2026, 7, 2, 12, 0, 0);
  try {
    const first = createEntitlementStore({ dataDir, publicKeys, accountsProvider: () => [], now });
    await first.initialize();
    const firstChallenge = await first.issueChallenge();
    await first.activate({
      challenge: firstChallenge.challenge,
      certificate: certificate(signingKeys.privateKey, firstChallenge, now()),
    });
    assert.equal((await first.getStatus()).tier, "pro");

    await deletePcApplicationData({ dataDir, logsDir: join(root, "logs") });

    const restored = createEntitlementStore({ dataDir, publicKeys, accountsProvider: () => [], now });
    await restored.initialize();
    const restoredChallenge = await restored.issueChallenge();
    assert.notEqual(restoredChallenge.backendKeyThumbprint, firstChallenge.backendKeyThumbprint);
    await restored.activate({
      challenge: restoredChallenge.challenge,
      certificate: certificate(signingKeys.privateKey, restoredChallenge, now()),
    });
    assert.equal((await restored.getStatus()).tier, "pro");
  } finally {
    await rm(root, { recursive: true, force: true });
  }
});

function certificate(privateKey, challenge, now) {
  const encode = (value) => Buffer.from(JSON.stringify(value)).toString("base64url");
  const header = { alg: "EdDSA", kid: "release-2026-01" };
  const issuedAt = Math.floor(now / 1000);
  const claims = {
    iss: "crm-entitlement-verifier",
    aud: "code-remote-manager-backend",
    productId: "pro_multi_account",
    maxAccounts: 5,
    backendKeyThumbprint: challenge.backendKeyThumbprint,
    challengeHash: createHash("sha256").update(challenge.challenge).digest("base64url"),
    iat: issuedAt,
    exp: issuedAt + (30 * 24 * 60 * 60),
    jti: `certificate-${challenge.backendKeyThumbprint}`,
  };
  const signingInput = `${encode(header)}.${encode(claims)}`;
  return `${signingInput}.${sign(null, Buffer.from(signingInput), privateKey).toString("base64url")}`;
}

async function write(path, value) {
  await mkdir(dirname(path), { recursive: true });
  await writeFile(path, value, "utf8");
}
