import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";
import { mkdir, rm } from "node:fs/promises";
import { join } from "node:path";
import { fileURLToPath } from "node:url";

import {
  ALL_TARGETS,
  archiveName,
  checksumName,
  computeChecksum,
  execName,
  packageTarget,
  parseTargets,
} from "../../scripts/release";

describe("parseTargets", () => {
  test("默认返回全部目标", () => {
    const targets = parseTargets([]);
    expect(targets).toEqual(ALL_TARGETS);
  });

  test("解析单一目标", () => {
    const targets = parseTargets(["--target", "linux-x64"]);
    expect(targets).toHaveLength(1);
    expect(targets[0]!.bunTarget).toBe("bun-linux-x64");
    expect(targets[0]!.os).toBe("linux");
    expect(targets[0]!.arch).toBe("x64");
  });

  test("解析多个逗号分隔目标", () => {
    const targets = parseTargets(["--target", "linux-x64,darwin-arm64,windows-x64"]);
    expect(targets).toHaveLength(3);
    expect(targets[0]!.bunTarget).toBe("bun-linux-x64");
    expect(targets[1]!.bunTarget).toBe("bun-darwin-arm64");
    expect(targets[2]!.bunTarget).toBe("bun-windows-x64");
  });

  test("解析 musl 变体", () => {
    const targets = parseTargets(["--target", "linux-x64-musl"]);
    expect(targets).toHaveLength(1);
    expect(targets[0]!.bunTarget).toBe("bun-linux-x64-musl");
    expect(targets[0]!.os).toBe("linux");
    expect(targets[0]!.arch).toBe("x64-musl");
  });

  test("无效 target 导致进程退出", () => {
    const exitCalls: number[] = [];
    const exitSpy = spyOn(process, "exit").mockImplementation(((code: number) => {
      exitCalls.push(code);
    }) as typeof process.exit);

    parseTargets(["--target", "invalid-target"]);
    exitSpy.mockRestore();

    expect(exitCalls).toEqual([1]);
  });
});

describe("execName", () => {
  const linuxTarget = ALL_TARGETS.find((t) => t.bunTarget === "bun-linux-x64")!;
  const windowsTarget = ALL_TARGETS.find((t) => t.bunTarget === "bun-windows-x64")!;
  const muslTarget = ALL_TARGETS.find((t) => t.bunTarget === "bun-linux-x64-musl")!;

  test("Linux x64 可执行文件命名", () => {
    expect(execName(linuxTarget, "0.1.0")).toBe("dial-server-0.1.0-linux-x64");
  });

  test("Windows 可执行文件命名带 .exe 后缀", () => {
    expect(execName(windowsTarget, "0.1.0")).toBe("dial-server-0.1.0-windows-x64.exe");
  });

  test("musl 变体命名", () => {
    expect(execName(muslTarget, "0.1.0")).toBe("dial-server-0.1.0-linux-x64-musl");
  });

  test("版本号正确嵌入", () => {
    expect(execName(linuxTarget, "1.2.3")).toBe("dial-server-1.2.3-linux-x64");
  });
});

describe("archiveName", () => {
  const linuxTarget = ALL_TARGETS.find((t) => t.bunTarget === "bun-linux-x64")!;
  const windowsTarget = ALL_TARGETS.find((t) => t.bunTarget === "bun-windows-x64")!;

  test("Linux 压缩包命名", () => {
    expect(archiveName(linuxTarget, "0.1.0")).toBe("dial-server_0.1.0_linux_x64.tar.gz");
  });

  test("Windows 压缩包命名", () => {
    expect(archiveName(windowsTarget, "0.1.0")).toBe("dial-server_0.1.0_windows_x64.tar.gz");
  });
});

describe("checksumName", () => {
  const linuxTarget = ALL_TARGETS.find((t) => t.bunTarget === "bun-linux-x64")!;

  test("校验和文件命名", () => {
    expect(checksumName(linuxTarget, "0.1.0")).toBe("dial-server_0.1.0_linux_x64.tar.gz.sha256");
  });
});

describe("computeChecksum", () => {
  const projectRoot = fileURLToPath(new URL("../../", import.meta.url));
  const packagesDir = join(projectRoot, "dist/release/packages");

  beforeEach(async () => {
    await mkdir(packagesDir, { recursive: true });
  });

  afterEach(async () => {
    await rm(packagesDir, { force: true, recursive: true });
  });

  test("生成正确格式的 sha256 文件", async () => {
    const archivePath = join(packagesDir, "dial-server_0.1.0_linux_x64.tar.gz");
    const content = Buffer.from("test archive content");
    await Bun.write(archivePath, content);

    const checksumPath = await computeChecksum(archivePath);
    const checksumContent = await Bun.file(checksumPath).text();

    const parts = checksumContent.split("  ");
    expect(parts).toHaveLength(2);
    expect(parts[0]).toMatch(/^[0-9a-f]{64}$/);
    expect(parts[1]).toBe("dial-server_0.1.0_linux_x64.tar.gz\n");
  });

  test("校验和与文件内容一致", async () => {
    const archivePath = join(packagesDir, "test.tar.gz");
    const content = Buffer.from("hello world");
    await Bun.write(archivePath, content);

    const checksumPath = await computeChecksum(archivePath);
    const checksumContent = await Bun.file(checksumPath).text();
    const hash = checksumContent.split("  ")[0]!;

    const expectedHash = Bun.CryptoHasher.hash("sha256", content, "hex");
    expect(hash).toBe(expectedHash);
  });
});

describe("packageTarget", () => {
  const projectRoot = fileURLToPath(new URL("../../", import.meta.url));
  const packagesDir = join(projectRoot, "dist/release/packages");

  beforeEach(async () => {
    await mkdir(packagesDir, { recursive: true });
  });

  afterEach(async () => {
    await rm(packagesDir, { force: true, recursive: true });
  });

  test("Linux 压缩包包含正确文件、目录前缀和权限位", async () => {
    const linuxTarget = ALL_TARGETS.find((t) => t.bunTarget === "bun-linux-x64")!;
    const binaryPath = join(packagesDir, "test-binary");
    await Bun.write(binaryPath, "binary content");

    const archivePath = await packageTarget(linuxTarget, "0.1.0", binaryPath);
    const archiveContent = await Bun.file(archivePath).arrayBuffer();

    const tar = await import("tar-stream");
    const zlib = await import("node:zlib");
    const extract = tar.extract();

    const entries: Array<{ mode: number; name: string }> = [];
    await new Promise<void>((resolve, reject) => {
      const gunzip = zlib.createGunzip();
      gunzip.write(Buffer.from(archiveContent));
      gunzip.end();
      gunzip.pipe(extract);
      extract.on("entry", (header: { mode?: number; name: string }, _stream: unknown, next: () => void) => {
        entries.push({ mode: header.mode ?? 0, name: header.name });
        next();
      });
      extract.on("finish", resolve);
      extract.on("error", reject);
    });

    const names = entries.map((e) => e.name);
    expect(names).toContain("dial-server_0.1.0_linux_x64/dial-server");
    expect(names).toContain("dial-server_0.1.0_linux_x64/probes.example.yaml");
    expect(names).toContain("dial-server_0.1.0_linux_x64/LICENSE");

    const binaryEntry = entries.find((e) => e.name.endsWith("/dial-server"))!;
    expect(binaryEntry.mode).toBe(0o755);

    const probesEntry = entries.find((e) => e.name.endsWith("probes.example.yaml"))!;
    expect(probesEntry.mode).toBe(0o644);
  });

  test("Windows 压缩包内可执行文件名为 dial-server.exe", async () => {
    const windowsTarget = ALL_TARGETS.find((t) => t.bunTarget === "bun-windows-x64")!;
    const binaryPath = join(packagesDir, "test-binary.exe");
    await Bun.write(binaryPath, "binary content");

    const archivePath = await packageTarget(windowsTarget, "0.1.0", binaryPath);
    const archiveContent = await Bun.file(archivePath).arrayBuffer();

    const tar = await import("tar-stream");
    const zlib = await import("node:zlib");
    const extract = tar.extract();

    const names: string[] = [];
    await new Promise<void>((resolve, reject) => {
      const gunzip = zlib.createGunzip();
      gunzip.write(Buffer.from(archiveContent));
      gunzip.end();
      gunzip.pipe(extract);
      extract.on("entry", (header: { name: string }, _stream: unknown, next: () => void) => {
        names.push(header.name);
        next();
      });
      extract.on("finish", resolve);
      extract.on("error", reject);
    });

    expect(names).toContain("dial-server_0.1.0_windows_x64/dial-server.exe");
  });
});
