import { createHash } from "node:crypto";
import { readFileSync, readdirSync, statSync } from "node:fs";
import { extname, join, relative } from "node:path";

const SHA40 = /^[a-f0-9]{40}$/i;
const SHA64 = /^[a-f0-9]{64}$/i;
const VERSION = /^[1-9][0-9]*\.[0-9]+\.[0-9]+\.[0-9]+$/;
const ABSOLUTE_PATH = /(?:[A-Za-z]:[\\/](?:Users|Documents)[\\/][^\\/"'\s]+[\\/]|\/Users\/[^/"'\s]+\/|\/home\/[^/"'\s]+\/)/;
const CREDENTIAL = /\b(?:sk|key)-[A-Za-z0-9_-]{16,}\b|-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/i;
const TEXT_EXTENSIONS = new Set([".cjs", ".cs", ".css", ".html", ".js", ".json", ".mjs", ".ts", ".tsx", ".txt", ".xml", ".yaml", ".yml"]);

export const DEFERRED_EXTERNAL_TASKS = Object.freeze(["TASK-WP7-03", "TASK-WP7-04"]);

export function buildFinalReleaseRecord({ appVersion, browsers, buildCommit, frozenFromCommit, recordedAt, windows }) {
  const record = {
    appVersion,
    browsers: browsers.map(({ brand, fullVersion }) => ({ brand, fullVersion })),
    buildCommit: buildCommit.toLowerCase(),
    deferredExternalTasks: [...DEFERRED_EXTERNAL_TASKS],
    finalRelease: true,
    fixedPort: 43121,
    frozenFromCommit: frozenFromCommit.toLowerCase(),
    recordedAt,
    releaseStatus: "first_version_internal",
    schemaVersion: "1.0",
    windows: { arch: windows.arch, build: windows.build, displayVersion: windows.displayVersion },
  };
  return validateFinalReleaseRecord(record);
}

export function validateFinalReleaseRecord(record) {
  const errors = [];
  if (record?.schemaVersion !== "1.0") errors.push("schemaVersion");
  if (record?.releaseStatus !== "first_version_internal") errors.push("releaseStatus");
  if (record?.finalRelease !== true) errors.push("finalRelease");
  if (record?.fixedPort !== 43121) errors.push("fixedPort");
  if (!SHA40.test(record?.buildCommit ?? "")) errors.push("buildCommit");
  if (!SHA40.test(record?.frozenFromCommit ?? "")) errors.push("frozenFromCommit");
  if (!Number.isFinite(Date.parse(record?.recordedAt ?? ""))) errors.push("recordedAt");
  if (!Array.isArray(record?.deferredExternalTasks) || record.deferredExternalTasks.join("|") !== DEFERRED_EXTERNAL_TASKS.join("|")) errors.push("deferredExternalTasks");
  if (record?.windows?.arch !== "x64" || !/^\d+\.\d+$/.test(record?.windows?.build ?? "")) errors.push("windows");
  if (!Array.isArray(record?.browsers) || record.browsers.length !== 2) {
    errors.push("browsers");
  } else {
    const brands = record.browsers.map(({ brand }) => brand).sort();
    if (brands.join("|") !== "Google Chrome|Microsoft Edge") errors.push("browserBrands");
    for (const browser of record.browsers) {
      if (!VERSION.test(browser.fullVersion ?? "")) errors.push(`${browser.brand}.fullVersion`);
      if ("path" in browser || "executablePath" in browser || "executableSha256" in browser) errors.push(`${browser.brand}.privateMetadata`);
    }
  }
  const serialized = JSON.stringify(record);
  if (ABSOLUTE_PATH.test(serialized) || CREDENTIAL.test(serialized)) errors.push("sensitiveValue");
  if (errors.length > 0) throw new Error(`WP7_07_RELEASE_INVALID:${[...new Set(errors)].join(",")}`);
  return record;
}

export function sha256File(path) {
  return createHash("sha256").update(readFileSync(path)).digest("hex").toUpperCase();
}

export function scanReleaseFiles({ roots, allowedFixturePaths = [] }) {
  const allowed = new Set(allowedFixturePaths.map((value) => value.replaceAll("\\", "/")));
  const findings = [];
  let scannedFiles = 0;
  function visit(root, current = root) {
    for (const entry of readdirSync(current, { withFileTypes: true })) {
      if ([".git", ".pnpm-store", "node_modules", "bin", "obj"].includes(entry.name)) continue;
      const path = join(current, entry.name);
      if (entry.isDirectory()) {
        visit(root, path);
        continue;
      }
      if (!entry.isFile()) continue;
      scannedFiles += 1;
      if (!TEXT_EXTENSIONS.has(extname(entry.name).toLowerCase())) continue;
      const logicalPath = relative(root, path).replaceAll("\\", "/");
      const content = readFileSync(path, "utf8");
      if (!allowed.has(logicalPath) && ABSOLUTE_PATH.test(content)) findings.push({ path: logicalPath, rule: "absolute_user_path" });
      if (!allowed.has(logicalPath) && CREDENTIAL.test(content)) findings.push({ path: logicalPath, rule: "credential_shape" });
    }
  }
  for (const root of roots) {
    if (!statSync(root).isDirectory()) throw new Error(`WP7_07_SCAN_ROOT_INVALID:${root}`);
    visit(root);
  }
  return { findings, scanned_files: scannedFiles, status: findings.length === 0 ? "passed" : "failed" };
}

export function validateFinalEvidence({ packageManifest, release, releaseSha256, scan }) {
  validateFinalReleaseRecord(release);
  if (!SHA64.test(releaseSha256 ?? "")) throw new Error("WP7_07_RELEASE_HASH_INVALID");
  if (packageManifest?.release_status !== release.releaseStatus || !SHA64.test(packageManifest?.zip_sha256 ?? "")) throw new Error("WP7_07_PACKAGE_MANIFEST_INVALID");
  if (scan?.status !== "passed" || scan.findings?.length !== 0) throw new Error("WP7_07_LEAK_SCAN_FAILED");
  return { release_sha256: releaseSha256.toUpperCase(), status: "passed", zip_sha256: packageManifest.zip_sha256.toUpperCase() };
}
