/**
 * Release Builder Agent Test — SF-07
 *
 * 验证 Release Package 生成器的所有关键功能。
 */

import { describe, it, before } from "node:test";
import assert from "node:assert/strict";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { existsSync, rmSync, readFileSync, readdirSync, statSync } from "node:fs";

const __dirname = resolve(fileURLToPath(new URL(".", import.meta.url)));
const WORKSPACE = resolve(__dirname, "..");

let buildRelease, writeRelease;

before(async () => {
  const mod = await import("../scripts/release-builder-agent.mjs");
  buildRelease = mod.buildRelease;
  writeRelease = mod.writeRelease;
});

// ═══════════════════════════════════════════════════════
// 1 — Release 目录结构生成
// ═══════════════════════════════════════════════════════

describe("1 — Release 目录结构生成", () => {
  let result;
  before(() => {
    result = buildRelease({ projectName: "PetCare" });
  });

  it("生成成功（无错误）", () => {
    assert.ok(!result.error, `Unexpected error: ${result.error}`);
    assert.ok(result.stats.totalFiles >= 8, `Expected >= 8 files, got ${result.stats.totalFiles}`);
  });

  it("包含 release/version.json", () => {
    assert.ok(result.files["release/version.json"], "Missing version.json");
  });

  it("包含 release/manifests/manifest.json", () => {
    assert.ok(result.files["release/manifests/manifest.json"], "Missing manifest.json");
  });

  it("包含 release/checksums/checksums.txt", () => {
    assert.ok(result.files["release/checksums/checksums.txt"], "Missing checksums.txt");
  });

  it("包含 release/release-notes/release-notes.md", () => {
    assert.ok(result.files["release/release-notes/release-notes.md"], "Missing release-notes.md");
  });

  it("包含 release/build-info.json", () => {
    assert.ok(result.files["release/build-info.json"], "Missing build-info.json");
  });

  it("包含 release/README.md", () => {
    assert.ok(result.files["release/README.md"], "Missing README.md");
  });
});

// ═══════════════════════════════════════════════════════
// 2 — version.json 验证
// ═══════════════════════════════════════════════════════

describe("2 — version.json 验证", () => {
  let result, version;
  before(() => {
    result = buildRelease({ projectName: "PetCare", version: "2.0.0", buildNumber: 5 });
    version = JSON.parse(result.files["release/version.json"]);
  });

  it("包含正确名称", () => {
    assert.equal(version.name, "petcare");
  });

  it("包含正确版本", () => {
    assert.equal(version.version, "2.0.0");
  });

  it("包含正确构建号", () => {
    assert.equal(version.buildNumber, 5);
  });

  it("包含所有平台", () => {
    assert.deepEqual(version.platforms, ["windows", "macos", "linux"]);
  });

  it("默认版本是 1.0.0", () => {
    const r = buildRelease({ projectName: "Test" });
    const v = JSON.parse(r.files["release/version.json"]);
    assert.equal(v.version, "1.0.0");
  });

  it("默认构建号是 1", () => {
    const r = buildRelease({ projectName: "Test" });
    const v = JSON.parse(r.files["release/version.json"]);
    assert.equal(v.buildNumber, 1);
  });
});

// ═══════════════════════════════════════════════════════
// 3 — manifest.json 验证
// ═══════════════════════════════════════════════════════

describe("3 — manifest.json 验证", () => {
  let result, manifest;
  before(() => {
    result = buildRelease({ projectName: "CRM" });
    manifest = JSON.parse(result.files["release/manifests/manifest.json"]);
  });

  it("包含项目名称", () => {
    assert.equal(manifest.project, "CRM");
  });

  it("包含版本", () => {
    assert.equal(manifest.version, "1.0.0");
  });

  it("包含生成时间", () => {
    assert.ok(manifest.generatedAt, "Missing generatedAt");
    assert.ok(!isNaN(new Date(manifest.generatedAt).getTime()), "Invalid date");
  });

  it("包含文件列表", () => {
    assert.ok(Array.isArray(manifest.files), "files should be array");
    assert.ok(manifest.files.length > 0, "files should not be empty");
  });

  it("每个文件包含 path, size, sha256", () => {
    for (const f of manifest.files) {
      assert.ok(f.path, "Missing path");
      assert.ok(typeof f.size === "number", "Missing size");
      assert.ok(f.sha256, "Missing sha256");
      assert.ok(f.sha256.length === 64, "SHA256 should be 64 hex chars");
    }
  });

  it("包含平台列表", () => {
    assert.deepEqual(manifest.platforms, ["windows", "macos", "linux"]);
  });

  it("包含生成器版本", () => {
    assert.ok(manifest.generatorVersion, "Missing generatorVersion");
  });

  it("totalFiles 与 files 数组长度一致", () => {
    assert.equal(manifest.totalFiles, manifest.files.length);
  });

  it("totalSize 是所有文件大小之和", () => {
    const expectedSize = manifest.files.reduce((s, f) => s + f.size, 0);
    assert.equal(manifest.totalSize, expectedSize);
  });
});

// ═══════════════════════════════════════════════════════
// 4 — checksums.txt 验证
// ═══════════════════════════════════════════════════════

describe("4 — checksums.txt 验证", () => {
  let result, checksums;
  before(() => {
    result = buildRelease({ projectName: "Inventory" });
    checksums = result.files["release/checksums/checksums.txt"];
  });

  it("非空", () => {
    assert.ok(checksums.length > 0, "Empty checksums");
  });

  it("每行格式为 SHA256  filename", () => {
    const lines = checksums.trim().split("\n");
    for (const line of lines) {
      const parts = line.split(/\s+/);
      assert.ok(parts.length >= 2, `Invalid line: ${line}`);
      assert.ok(parts[0].length === 64, `Invalid SHA256: ${parts[0]}`);
      assert.ok(parts[1].length > 0, `Missing filename`);
    }
  });

  it("行数与 manifest 文件数一致", () => {
    const manifest = JSON.parse(result.files["release/manifests/manifest.json"]);
    const lines = checksums.trim().split("\n");
    assert.equal(lines.length, manifest.files.length);
  });

  it("SHA256 与 manifest 一致", () => {
    const manifest = JSON.parse(result.files["release/manifests/manifest.json"]);
    const lines = checksums.trim().split("\n");
    for (let i = 0; i < lines.length; i++) {
      const [sha, path] = lines[i].split(/\s+/);
      assert.equal(sha, manifest.files[i].sha256, `SHA mismatch for ${path}`);
      assert.equal(path, manifest.files[i].path, `Path mismatch at line ${i}`);
    }
  });
});

// ═══════════════════════════════════════════════════════
// 5 — release-notes.md 验证
// ═══════════════════════════════════════════════════════

describe("5 — release-notes.md 验证", () => {
  let result, notes;
  before(() => {
    result = buildRelease({ projectName: "PetCare" });
    notes = result.files["release/release-notes/release-notes.md"];
  });

  it("非空", () => {
    assert.ok(notes.length > 0, "Empty release notes");
  });

  it("包含项目名称", () => {
    assert.ok(notes.includes("PetCare"), "Missing project name");
  });

  it("包含版本信息", () => {
    assert.ok(notes.includes("1.0.0"), "Missing version");
  });

  it("包含功能列表", () => {
    assert.ok(notes.includes("Features"), "Missing features section");
  });

  it("包含构建时间", () => {
    assert.ok(notes.includes("Build Time"), "Missing build time");
  });

  it("包含平台信息", () => {
    assert.ok(notes.includes("windows"), "Missing windows");
    assert.ok(notes.includes("macos"), "Missing macos");
    assert.ok(notes.includes("linux"), "Missing linux");
  });

  it("包含安装方式", () => {
    assert.ok(notes.includes("Installation"), "Missing installation section");
    assert.ok(notes.includes(".exe"), "Missing Windows installer");
    assert.ok(notes.includes(".dmg"), "Missing macOS DMG");
    assert.ok(notes.includes(".AppImage"), "Missing Linux AppImage");
  });

  it("包含校验和引用", () => {
    assert.ok(notes.includes("checksums"), "Missing checksums reference");
  });
});

// ═══════════════════════════════════════════════════════
// 6 — build-info.json 验证
// ═══════════════════════════════════════════════════════

describe("6 — build-info.json 验证", () => {
  let result, buildInfo;
  before(() => {
    result = buildRelease({ projectName: "CRM" });
    buildInfo = JSON.parse(result.files["release/build-info.json"]);
  });

  it("包含 Node Version", () => {
    assert.ok(buildInfo.nodeVersion, "Missing nodeVersion");
    assert.ok(buildInfo.nodeVersion.startsWith("v"), "nodeVersion should start with v");
  });

  it("包含 Generator Version", () => {
    assert.ok(buildInfo.generatorVersion, "Missing generatorVersion");
  });

  it("包含 Build Time", () => {
    assert.ok(buildInfo.buildTime, "Missing buildTime");
    assert.ok(!isNaN(new Date(buildInfo.buildTime).getTime()), "Invalid buildTime");
  });

  it("包含 Domain", () => {
    assert.ok("domain" in buildInfo, "Missing domain");
  });

  it("包含 Entity Count", () => {
    assert.ok("entityCount" in buildInfo, "Missing entityCount");
    assert.ok(typeof buildInfo.entityCount === "number", "entityCount should be number");
  });

  it("包含 Route Count", () => {
    assert.ok("routeCount" in buildInfo, "Missing routeCount");
    assert.ok(typeof buildInfo.routeCount === "number", "routeCount should be number");
  });

  it("包含 Screen Count", () => {
    assert.ok("screenCount" in buildInfo, "Missing screenCount");
    assert.ok(typeof buildInfo.screenCount === "number", "screenCount should be number");
  });
});

// ═══════════════════════════════════════════════════════
// 7 — Platform Artifact Layout
// ═══════════════════════════════════════════════════════

describe("7 — Platform Artifact Layout", () => {
  let result;
  before(() => {
    result = buildRelease({ projectName: "PetCare" });
  });

  it("包含 Windows 目录文件", () => {
    const winFiles = Object.keys(result.files).filter(p => p.startsWith("release/windows/"));
    assert.ok(winFiles.length >= 2, `Expected >= 2 Windows files, got ${winFiles.length}`);
  });

  it("包含 macOS 目录文件", () => {
    const macFiles = Object.keys(result.files).filter(p => p.startsWith("release/macos/"));
    assert.ok(macFiles.length >= 2, `Expected >= 2 macOS files, got ${macFiles.length}`);
  });

  it("包含 Linux 目录文件", () => {
    const linuxFiles = Object.keys(result.files).filter(p => p.startsWith("release/linux/"));
    assert.ok(linuxFiles.length >= 2, `Expected >= 2 Linux files, got ${linuxFiles.length}`);
  });

  it("Windows 文件包含安装器占位", () => {
    const winExe = Object.entries(result.files).find(([p]) => p.includes("windows") && p.includes("setup"));
    assert.ok(winExe, "Missing Windows setup placeholder");
    assert.ok(winExe[1].includes("PLACEHOLDER"), "Should be placeholder");
  });

  it("macOS 文件包含 DMG 占位", () => {
    const macDmg = Object.entries(result.files).find(([p]) => p.includes("macos") && p.endsWith(".dmg"));
    assert.ok(macDmg, "Missing macOS DMG placeholder");
    assert.ok(macDmg[1].includes("PLACEHOLDER"), "Should be placeholder");
  });

  it("Linux 文件包含 AppImage 占位", () => {
    const linuxApp = Object.entries(result.files).find(([p]) => p.includes("linux") && p.includes("AppImage"));
    assert.ok(linuxApp, "Missing Linux AppImage placeholder");
    assert.ok(linuxApp[1].includes("PLACEHOLDER"), "Should be placeholder");
  });

  it("所有平台文件非空", () => {
    const platformFiles = Object.entries(result.files).filter(([p]) =>
      p.startsWith("release/windows/") || p.startsWith("release/macos/") || p.startsWith("release/linux/")
    );
    for (const [path, content] of platformFiles) {
      assert.ok(content.length > 0, `${path}: empty content`);
    }
  });
});

// ═══════════════════════════════════════════════════════
// 8 — 文件写入 I/O
// ═══════════════════════════════════════════════════════

describe("8 — 文件写入 I/O", () => {
  it("writeRelease 写入完整目录树", () => {
    const result = buildRelease({ projectName: "TestApp" });
    const tmpDir = resolve(WORKSPACE, ".tmp-release-test");

    writeRelease(result, tmpDir);

    assert.ok(existsSync(resolve(tmpDir, "release", "version.json")));
    assert.ok(existsSync(resolve(tmpDir, "release", "manifests", "manifest.json")));
    assert.ok(existsSync(resolve(tmpDir, "release", "checksums", "checksums.txt")));
    assert.ok(existsSync(resolve(tmpDir, "release", "release-notes", "release-notes.md")));
    assert.ok(existsSync(resolve(tmpDir, "release", "build-info.json")));
    assert.ok(existsSync(resolve(tmpDir, "release", "README.md")));

    // Platform dirs
    const winDir = resolve(tmpDir, "release", "windows");
    const macDir = resolve(tmpDir, "release", "macos");
    const linuxDir = resolve(tmpDir, "release", "linux");
    assert.ok(existsSync(winDir), "Missing windows dir");
    assert.ok(existsSync(macDir), "Missing macos dir");
    assert.ok(existsSync(linuxDir), "Missing linux dir");

    // Platform files
    const winFiles = readdirSync(winDir);
    assert.ok(winFiles.length >= 2, "Windows should have >= 2 files");
    const macFiles = readdirSync(macDir);
    assert.ok(macFiles.length >= 2, "macOS should have >= 2 files");
    const linuxFiles = readdirSync(linuxDir);
    assert.ok(linuxFiles.length >= 2, "Linux should have >= 2 files");

    rmSync(tmpDir, { recursive: true, force: true });
  });

  it("所有生成的文件非空", () => {
    const result = buildRelease();
    for (const [path, content] of Object.entries(result.files)) {
      assert.ok(content.length > 0, `${path}: empty content`);
    }
  });
});

// ═══════════════════════════════════════════════════════
// 9 — Stats 验证
// ═══════════════════════════════════════════════════════

describe("9 — Stats 验证", () => {
  it("统计正确", () => {
    const result = buildRelease({ projectName: "PetCare" });
    const stats = result.stats;

    assert.ok(stats.totalFiles > 0, "totalFiles should be > 0");
    assert.ok(stats.platformFiles >= 6, "platformFiles should be >= 6 (2 per platform)");
    assert.ok(stats.manifestFiles >= 1, "manifestFiles should be >= 1");
    assert.ok(stats.checksumFiles >= 1, "checksumFiles should be >= 1");
    assert.ok(stats.releaseNotesFiles >= 1, "releaseNotesFiles should be >= 1");
    assert.ok(stats.metadataFiles >= 3, "metadataFiles should be >= 3 (version, manifest, build-info)");
    assert.ok(stats.totalSize > 0, "totalSize should be > 0");
  });
});

// ═══════════════════════════════════════════════════════
// 10 — 多领域覆盖
// ═══════════════════════════════════════════════════════

describe("10 — 多领域覆盖（PetCare / CRM / Inventory）", () => {
  const domains = [
    { name: "PetCare", expected: "petcare" },
    { name: "CRM", expected: "crm" },
    { name: "Inventory", expected: "inventory" },
  ];

  for (const { name, expected } of domains) {
    it(`${name} — 生成成功`, () => {
      const result = buildRelease({ projectName: name });
      assert.ok(!result.error, `${name}: ${result.message}`);
      assert.ok(result.files["release/version.json"], `${name}: missing version.json`);
      assert.ok(result.files["release/manifests/manifest.json"], `${name}: missing manifest.json`);
      assert.ok(result.files["release/checksums/checksums.txt"], `${name}: missing checksums.txt`);
      assert.ok(result.files["release/release-notes/release-notes.md"], `${name}: missing release-notes.md`);
      assert.ok(result.files["release/build-info.json"], `${name}: missing build-info.json`);
    });

    it(`${name} — version.json 包含正确名称`, () => {
      const result = buildRelease({ projectName: name });
      const version = JSON.parse(result.files["release/version.json"]);
      assert.equal(version.name, expected, `${name}: expected '${expected}', got '${version.name}'`);
    });

    it(`${name} — manifest 包含正确项目名`, () => {
      const result = buildRelease({ projectName: name });
      const manifest = JSON.parse(result.files["release/manifests/manifest.json"]);
      assert.equal(manifest.project, name, `${name}: expected '${name}', got '${manifest.project}'`);
    });

    it(`${name} — 包含三个平台目录`, () => {
      const result = buildRelease({ projectName: name });
      const paths = Object.keys(result.files);
      assert.ok(paths.some(p => p.startsWith("release/windows/")), `${name}: missing windows/`);
      assert.ok(paths.some(p => p.startsWith("release/macos/")), `${name}: missing macos/`);
      assert.ok(paths.some(p => p.startsWith("release/linux/")), `${name}: missing linux/`);
    });
  }
});

// ═══════════════════════════════════════════════════════
// 11 — PRD 集成（从项目目录读取元数据）
// ═══════════════════════════════════════════════════════

describe("11 — PRD 集成", () => {
  it("从 prd.json 读取项目信息", () => {
    const petcareDir = resolve(WORKSPACE, ".benchmark", "petcare");
    if (!existsSync(resolve(petcareDir, "prd.json"))) return; // skip if no benchmark data

    const result = buildRelease({ projectDir: petcareDir });
    assert.ok(!result.error, `Error: ${result.error}`);

    const version = JSON.parse(result.files["release/version.json"]);
    assert.equal(version.name, "petcare", "Should read name from PRD");

    const buildInfo = JSON.parse(result.files["release/build-info.json"]);
    assert.ok(buildInfo.entityCount > 0, "Should have entities from PRD");
    assert.ok(buildInfo.screenCount > 0, "Should have screens from PRD");
  });

  it("从 crm benchmark 读取元数据", () => {
    const crmDir = resolve(WORKSPACE, ".benchmark", "crm");
    if (!existsSync(resolve(crmDir, "prd.json"))) return;

    const result = buildRelease({ projectDir: crmDir });
    assert.ok(!result.error, `Error: ${result.error}`);

    const buildInfo = JSON.parse(result.files["release/build-info.json"]);
    assert.ok(buildInfo.entityCount > 0, "Should have entities");
  });

  it("从 inventory benchmark 读取元数据", () => {
    const invDir = resolve(WORKSPACE, ".benchmark", "inventory");
    if (!existsSync(resolve(invDir, "prd.json"))) return;

    const result = buildRelease({ projectDir: invDir });
    assert.ok(!result.error, `Error: ${result.error}`);

    const buildInfo = JSON.parse(result.files["release/build-info.json"]);
    assert.ok(buildInfo.entityCount > 0, "Should have entities");
  });
});
