import assert from "node:assert/strict";
import {spawnSync} from "node:child_process";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import {setTimeout as delay} from "node:timers/promises";
import {test, afterEach} from "node:test";
import {withFreeSkillLock} from "./free-skill-lock.ts";
import {createFreeSkillToolFactory} from "./free-skill-tool.ts";

const roots: string[] = [];
afterEach(async () => {await Promise.all(roots.splice(0).map(root => fs.rm(root, {recursive: true, force: true})));});
async function workspace() {
  const root = await fs.mkdtemp(path.join(os.tmpdir(), "skill-lock-review-"));
  roots.push(root);
  return root;
}
async function fakeOwner(root: string, pid: number, createdAt: string) {
  const lock = path.join(root, ".xg-workspace/skills/locks/write");
  await fs.mkdir(lock, {recursive: true});
  await fs.writeFile(path.join(lock, "owner.json"), JSON.stringify({pid, createdAt}));
}
for (const kind of ["死亡进程", "过期时间戳"]) {
  test(`${kind}残留锁可自动回收并成功创建 Skill`, async () => {
    const root = await workspace();
    const child = spawnSync(process.execPath, ["-e", ""]);
    assert.equal(child.status, 0);
    assert.throws(() => process.kill(child.pid, 0), (error: any) => error.code === "ESRCH");
    await fakeOwner(root, kind === "死亡进程" ? child.pid : process.pid,
      new Date(Date.now() - (kind === "过期时间戳" ? 11 * 60 * 1000 : 0)).toISOString());
    const result = await createFreeSkillToolFactory({workspaceDir: root}).execute("create", {
      skillName: "xg-userscope-demo", displayName: "示例", content: "---\nname: xg-userscope-demo\ndescription: 示例\n---\n\n正文",
    });
    assert.equal(result.details.success, true);
    await assert.rejects(fs.access(path.join(root, ".xg-workspace/skills/locks/write")));
  });
}

test("未过期活跃 pid 不被抢占，等待三秒后失败", async () => {
  const root = await workspace();
  await fakeOwner(root, process.pid, new Date().toISOString());
  const started = Date.now();
  let called = false;
  await assert.rejects(withFreeSkillLock(root, async () => {called = true;}), /正在写入/);
  assert.equal(called, false);
  assert.ok(Date.now() - started >= 2900);
  assert.equal(JSON.parse(await fs.readFile(path.join(root, ".xg-workspace/skills/locks/write/owner.json"), "utf8")).pid, process.pid);
});

test("持有者短暂操作结束后等待者成功获得锁", async () => {
  const root = await workspace();
  let waiting: Promise<string> | undefined;
  await withFreeSkillLock(root, async () => {
    const owner = JSON.parse(await fs.readFile(path.join(root, ".xg-workspace/skills/locks/write/owner.json"), "utf8"));
    assert.equal(owner.pid, process.pid);
    assert.ok(Number.isFinite(Date.parse(owner.createdAt)));
    waiting = withFreeSkillLock(root, async () => "获得锁");
    await delay(150);
  });
  assert.equal(await waiting, "获得锁");
});

test("锁清理失败保留 action 的原始错误", async () => {
  const root = await workspace();
  const original = new Error("业务错误");
  await assert.rejects(withFreeSkillLock(root, async () => {
    await fs.writeFile(path.join(root, ".xg-workspace/skills/locks/write/extra"), "阻止 rmdir");
    throw original;
  }), error => error === original);
});

test("回收标记自身过期时也可清理并获取锁", async () => {
  const root = await workspace();
  await fakeOwner(root, process.pid, new Date(Date.now() - 11 * 60 * 1000).toISOString());
  const claim = path.join(root, ".xg-workspace/skills/locks/write/.reclaim");
  await fs.mkdir(claim);
  const old = new Date(Date.now() - 11 * 60 * 1000);
  await fs.utimes(claim, old, old);
  assert.equal(await withFreeSkillLock(root, async () => "成功"), "成功");
});

test("旧持有者退出时不删除替代持有者的锁", async () => {
  const root = await workspace();
  const ownerPath = path.join(root, ".xg-workspace/skills/locks/write/owner.json");
  await withFreeSkillLock(root, async () => {
    await fs.writeFile(ownerPath, JSON.stringify({pid: process.pid, createdAt: new Date().toISOString(), token: "replacement"}));
  });
  assert.equal(JSON.parse(await fs.readFile(ownerPath, "utf8")).token, "replacement");
});
