/**
 * @file Tests for server/lib/lane-mcp.js: relocating a source repo's
 * ~/.claude.json MCP server config into a lane's own .mcp.json, pinning
 * Playwright's proof output dir, and seeding Chromium profiles. Uses a real
 * temp $HOME (via process.env.HOME override) so the module's own
 * os.homedir()-based path resolution is exercised, not mocked around.
 * @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
 */

const { describe, it, before, after } = require("node:test");
const assert = require("node:assert/strict");
const fs = require("node:fs");
const os = require("node:os");
const path = require("node:path");

const SUITE_ROOT = fs.mkdtempSync(path.join(os.tmpdir(), "ccam-mcp-"));
const FAKE_HOME = path.join(SUITE_ROOT, "home");
fs.mkdirSync(FAKE_HOME, { recursive: true });
process.env.HOME = FAKE_HOME;

const laneMcp = require("../lib/lane-mcp");

after(() => fs.rmSync(SUITE_ROOT, { recursive: true, force: true }));

function writeClaudeJson(projects) {
  fs.writeFileSync(path.join(FAKE_HOME, ".claude.json"), JSON.stringify({ projects }, null, 2));
}

let laneSeq = 0;
function makeLane(sourceRepo) {
  laneSeq += 1;
  const cwd = path.join(SUITE_ROOT, `lane-cwd-${laneSeq}`);
  fs.mkdirSync(cwd, { recursive: true });
  return { cwd, source_repo: sourceRepo };
}

describe("syncMcp — reading and relocating", () => {
  it("throws ENOMCPCONFIG when the source repo has no mcpServers", async () => {
    const sourceRepo = path.join(SUITE_ROOT, "src-none");
    fs.mkdirSync(sourceRepo, { recursive: true });
    writeClaudeJson({ [sourceRepo]: {} });
    await assert.rejects(() => laneMcp.syncMcp(makeLane(sourceRepo)), {
      code: "ENOMCPCONFIG",
    });
  });

  it("throws ENOMCPCONFIG when ~/.claude.json doesn't exist at all", async () => {
    fs.rmSync(path.join(FAKE_HOME, ".claude.json"), { force: true });
    await assert.rejects(() => laneMcp.syncMcp(makeLane(path.join(SUITE_ROOT, "src-missing"))), {
      code: "ENOMCPCONFIG",
    });
  });

  it("relocates absolute paths under source_repo to the lane's cwd", async () => {
    const sourceRepo = path.join(SUITE_ROOT, "src-relocate");
    fs.mkdirSync(sourceRepo, { recursive: true });
    writeClaudeJson({
      [sourceRepo]: {
        mcpServers: {
          playwright: {
            command: "npx",
            args: [
              "-y",
              "@playwright/mcp@latest",
              "--user-data-dir",
              `${sourceRepo}/.playwright-mcp/profiles/default`,
            ],
          },
        },
      },
    });
    const lane = makeLane(sourceRepo);
    const result = await laneMcp.syncMcp(lane);
    assert.deepEqual(result.servers, ["playwright"]);

    const written = JSON.parse(fs.readFileSync(path.join(lane.cwd, ".mcp.json"), "utf8"));
    assert.equal(
      written.mcpServers.playwright.args[3],
      `${lane.cwd}/.playwright-mcp/profiles/default`
    );
  });

  it("falls back to the lane's own cwd when source_repo is null (adopted lane)", async () => {
    const lane = makeLane(null);
    writeClaudeJson({ [lane.cwd]: { mcpServers: { playwright: { command: "npx", args: [] } } } });
    const result = await laneMcp.syncMcp(lane);
    assert.deepEqual(result.servers, ["playwright"]);
  });
});

describe("syncMcp — Playwright output-dir pinning", () => {
  it("pins --output-dir only when a @playwright/mcp server doesn't already declare one", async () => {
    const sourceRepo = path.join(SUITE_ROOT, "src-pin");
    fs.mkdirSync(sourceRepo, { recursive: true });
    writeClaudeJson({
      [sourceRepo]: {
        mcpServers: {
          playwright: { command: "npx", args: ["-y", "@playwright/mcp@latest"] },
          "playwright-custom": {
            command: "npx",
            args: ["-y", "@playwright/mcp@latest", "--output-dir", "/already/set"],
          },
          "not-playwright": { command: "npx", args: ["-y", "some-other-mcp"] },
        },
      },
    });
    const lane = makeLane(sourceRepo);
    await laneMcp.syncMcp(lane);
    const written = JSON.parse(fs.readFileSync(path.join(lane.cwd, ".mcp.json"), "utf8"));

    assert.deepEqual(written.mcpServers.playwright.args.slice(-2), [
      "--output-dir",
      path.join(lane.cwd, ".playwright-mcp"),
    ]);
    assert.deepEqual(written.mcpServers["playwright-custom"].args.slice(-2), [
      "--output-dir",
      "/already/set",
    ]);
    assert.equal(written.mcpServers["not-playwright"].args.includes("--output-dir"), false);
  });
});

describe("syncMcp — profile seeding", () => {
  it("seeds a source profile into the lane and strips Singleton* lock files", async () => {
    const sourceRepo = path.join(SUITE_ROOT, "src-profiles");
    const srcProfileDir = path.join(sourceRepo, ".playwright-mcp", "profiles", "default");
    fs.mkdirSync(srcProfileDir, { recursive: true });
    fs.writeFileSync(path.join(srcProfileDir, "Cookies"), "fake-cookie-db");
    fs.writeFileSync(path.join(srcProfileDir, "SingletonLock"), "stale-lock");
    writeClaudeJson({
      [sourceRepo]: { mcpServers: { playwright: { command: "npx", args: [] } } },
    });

    const lane = makeLane(sourceRepo);
    const result = await laneMcp.syncMcp(lane);
    assert.deepEqual(result.profilesSeeded, ["default"]);

    const destDir = path.join(lane.cwd, ".playwright-mcp", "profiles", "default");
    assert.ok(fs.existsSync(path.join(destDir, "Cookies")));
    assert.ok(!fs.existsSync(path.join(destDir, "SingletonLock")));
  });

  it("never overwrites a profile that already exists at the destination", async () => {
    const sourceRepo = path.join(SUITE_ROOT, "src-profiles-2");
    const srcProfileDir = path.join(sourceRepo, ".playwright-mcp", "profiles", "default");
    fs.mkdirSync(srcProfileDir, { recursive: true });
    fs.writeFileSync(path.join(srcProfileDir, "Cookies"), "new-cookie-db");
    writeClaudeJson({
      [sourceRepo]: { mcpServers: { playwright: { command: "npx", args: [] } } },
    });

    const lane = makeLane(sourceRepo);
    const destDir = path.join(lane.cwd, ".playwright-mcp", "profiles", "default");
    fs.mkdirSync(destDir, { recursive: true });
    fs.writeFileSync(path.join(destDir, "Cookies"), "already-logged-in-cookie-db");

    const result = await laneMcp.syncMcp(lane);
    assert.deepEqual(result.profilesSeeded, []);
    assert.equal(
      fs.readFileSync(path.join(destDir, "Cookies"), "utf8"),
      "already-logged-in-cookie-db"
    );
  });
});

describe("syncMcp — .git/info/exclude idempotency", () => {
  it("appends .mcp.json once and does not duplicate it on a second call", async () => {
    const sourceRepo = path.join(SUITE_ROOT, "src-exclude");
    fs.mkdirSync(sourceRepo, { recursive: true });
    writeClaudeJson({
      [sourceRepo]: { mcpServers: { playwright: { command: "npx", args: [] } } },
    });
    const lane = makeLane(sourceRepo);
    fs.mkdirSync(path.join(lane.cwd, ".git"), { recursive: true });

    await laneMcp.syncMcp(lane);
    await laneMcp.syncMcp(lane);

    const exclude = fs.readFileSync(path.join(lane.cwd, ".git", "info", "exclude"), "utf8");
    const matches = exclude.split("\n").filter((line) => line === ".mcp.json");
    assert.equal(matches.length, 1);
  });
});
