import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import { comparePackageVersions, getPiWebStatus, getPiWebVersionStatus } from "./piWebStatus.js";
import { SessionDaemonClient } from "../sessiond/sessionDaemonClient.js";
import type { PiWebComponentStatus } from "../shared/apiTypes.js";

const originalSkipVersionCheck = process.env["PI_WEB_SKIP_VERSION_CHECK"];
const originalHome = process.env["HOME"];

afterEach(() => {
  restoreEnv("PI_WEB_SKIP_VERSION_CHECK", originalSkipVersionCheck);
  restoreEnv("HOME", originalHome);
  vi.restoreAllMocks();
});

describe("PI WEB status", () => {
  it("compares semver-shaped CalVer versions", () => {
    expect(comparePackageVersions("1.202605.9", "1.202605.8")).toBeGreaterThan(0);
    expect(comparePackageVersions("1.202605.8", "1.202605.8")).toBe(0);
    expect(comparePackageVersions("1.202605.7", "1.202605.8")).toBeLessThan(0);
  });

  it("returns installed and running version components without release metadata", async () => {
    const daemon = daemonWithComponent({
      component: "sessiond",
      label: "Session daemon",
      runtimeVersion: "1.202605.7",
      installedVersion: "1.202605.8",
      stale: true,
      available: true,
    });

    const status = await getPiWebVersionStatus(daemon);

    expect(status.packageName).toBe("@jmfederico/pi-web");
    expect(status.components.web.component).toBe("web");
    expect(status.components.sessiond.runtimeVersion).toBe("1.202605.7");
    expect(status).not.toHaveProperty("release");
  });

  it("reports stale session daemon versions as messages", async () => {
    process.env["PI_WEB_SKIP_VERSION_CHECK"] = "1";
    const daemon = daemonWithComponent({
      component: "sessiond",
      label: "Session daemon",
      runtimeVersion: "1.202605.7",
      installedVersion: "1.202605.8",
      stale: true,
      available: true,
      installation: { kind: "pi-package", source: "npm:@jmfederico/pi-web", scope: "user", path: "/tmp/pi-web" },
    });

    const status = await getPiWebStatus(daemon);

    expect(status.release.skipped).toBe(true);
    expect(status.components.sessiond.stale).toBe(true);
    expect(status.components.sessiond.installation).toMatchObject({ kind: "pi-package", source: "npm:@jmfederico/pi-web", scope: "user" });
    expect(status.messages.map((message) => message.id)).toContain("sessiond-stale");
  });

  it("suggests native systemd commands for local development services", async () => {
    if (process.platform !== "linux") return;
    process.env["PI_WEB_SKIP_VERSION_CHECK"] = "1";
    const home = await tempHome();
    try {
      process.env["HOME"] = home;
      await installSystemdServiceFiles(home, ["pi-web-sessiond.service", "pi-web-ui-dev.service"]);
      const daemon = daemonWithComponent(staleLocalSessiond());

      const status = await getPiWebStatus(daemon);

      expect(status.commands.restart).toBe("systemctl --user restart pi-web-sessiond.service pi-web-ui-dev.service");
      expect(status.commands.restartWeb).toBe("systemctl --user restart pi-web-ui-dev.service");
      expect(status.commands.restartSessiond).toBe("systemctl --user restart pi-web-sessiond.service");
      expect(status.messages.find((message) => message.id === "sessiond-stale")?.command).toBe("systemctl --user restart pi-web-sessiond.service");
    } finally {
      await rm(home, { recursive: true, force: true });
    }
  });

  it("omits local restart commands when no native service command is known", async () => {
    process.env["PI_WEB_SKIP_VERSION_CHECK"] = "1";
    const home = await tempHome();
    try {
      process.env["HOME"] = home;
      const daemon = daemonWithComponent(staleLocalSessiond());

      const status = await getPiWebStatus(daemon);
      const staleMessage = status.messages.find((message) => message.id === "sessiond-stale");

      expect(status.commands.restart).toBeUndefined();
      expect(staleMessage?.command).toBeUndefined();
      expect(JSON.stringify(status)).not.toContain("pi-web restart");
    } finally {
      await rm(home, { recursive: true, force: true });
    }
  });
});

function daemonWithComponent(component: PiWebComponentStatus): SessionDaemonClient {
  const daemon = new SessionDaemonClient();
  vi.spyOn(daemon, "request").mockResolvedValue({
    statusCode: 200,
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ version: component }),
  });
  return daemon;
}

function staleLocalSessiond(): PiWebComponentStatus {
  return {
    component: "sessiond",
    label: "Session daemon",
    runtimeVersion: "1.202605.7",
    installedVersion: "1.202605.8",
    stale: true,
    available: true,
    installation: { kind: "local", path: "/srv/dev/pi-web" },
  };
}

async function tempHome(): Promise<string> {
  return await mkdtemp(join(tmpdir(), "pi-web-status-"));
}

async function installSystemdServiceFiles(home: string, names: string[]): Promise<void> {
  const dir = join(home, ".config", "systemd", "user");
  await mkdir(dir, { recursive: true });
  await Promise.all(names.map((name) => writeFile(join(dir, name), "")));
}

function restoreEnv(key: string, value: string | undefined): void {
  if (value === undefined) Reflect.deleteProperty(process.env, key);
  else process.env[key] = value;
}
