import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { spawnSync } from "node:child_process";
import test from "node:test";

const root = new URL("../", import.meta.url);
const routes = ["", "download", "privacy", "terms", "support", "delete-account", "licenses"];

async function buildPublicPages(t) {
  const output = await mkdtemp(join(tmpdir(), "crm-public-site-"));
  t.after(() => rm(output, { recursive: true, force: true }));
  const result = spawnSync(process.execPath, ["website/build.mjs", "--out", output], {
    cwd: root,
    encoding: "utf8",
  });
  assert.equal(result.status, 0, result.stderr || result.stdout);
  return new Map(await Promise.all(routes.map(async (route) => [
    route,
    await readFile(join(output, route, "index.html"), "utf8"),
  ])));
}

test("public site build produces the seven release routes with synchronized policy content", async (t) => {
  const pages = await buildPublicPages(t);

  for (const [route, html] of pages) {
    assert.match(html, /<meta name="viewport" content="width=device-width, initial-scale=1">/, route);
    assert.match(html, /href="\/assets\/site\.[a-f0-9]{12}\.css"/, route);
    assert.doesNotMatch(html, /href="\/assets\/site\.css"/, route);
    assert.match(html, /href="mailto:coderemote@makecdev\.com"/, route);
    assert.match(html, /href="\/privacy"/, route);
    assert.match(html, /href="\/terms"/, route);
    assert.match(html, /href="\/support"/, route);
    assert.match(html, /href="\/delete-account"/, route);
    assert.match(html, /href="\/licenses"/, route);
    assert.match(html, /href="\/download"/, route);
    assert.doesNotMatch(html, /makecdev@gmail\.com|만 18세|at least 18|Operator:/i, route);
    assert.match(
      html,
      /<a class="brand" href="\/" aria-label="Code Remote Manager 홈">Code Remote Manager<\/a>/,
      route,
    );
    assert.doesNotMatch(html, /class="brand-accent"/, route);
    assert.match(html, /© 2026 Code Remote Manager/, route);
    assert.doesNotMatch(html, /class="brand-mark"|최종 수정 2026년|Updated July 30, 2026/, route);
  }

  assert.match(pages.get(""), /주요 기능.*작동 방식.*필요 환경.*보안 구조.*자주 묻는 질문/s);
  assert.match(pages.get(""), /Key features.*How it works.*Requirements.*Security.*Frequently asked questions/s);
  assert.match(pages.get(""), /<title>Code Remote Manager<\/title>/);
  assert.match(pages.get(""), /무료.*원격 기능.*계정 1개.*Pro.*계정 5개/s);
  assert.match(pages.get(""), /href="\/download\/CodeRemoteManagerSetup\.zip"/);
  assert.doesNotMatch(pages.get(""), /href="\/download\/CodeRemoteManagerSetup\.exe"/);
  assert.match(pages.get(""), /설치 방법을 선택하세요.*<span>EXE 포함 ZIP<\/span><strong>간편 설치.*<span>EXE 없음<\/span><strong>직접 설치.*Tailscale/s);
  assert.match(pages.get(""), /Choose how to install.*<span>EXE in ZIP<\/span><strong>Quick install.*<span>No EXE<\/span><strong>Manual install.*Tailscale/is);
  assert.equal((pages.get("").match(/<dt>/g) ?? []).length, 6);
  assert.doesNotMatch(pages.get(""), /MakeCdev가 내 Chat|Does MakeCdev store/i);
  assert.doesNotMatch(pages.get(""), /출시 준비|현재 출시 버전|current release|card-grid/i);
  assert.match(pages.get("download"), /Windows 1\.0\.0.*공개 unsigned.*SmartScreen/s);
  assert.match(pages.get("download"), /Get-FileHash -Algorithm SHA256 \.\\CodeRemoteManagerSetup\.exe/);
  assert.match(pages.get("download"), /href="\/download\/release-manifest\.json"/);
  assert.match(pages.get("download"), /href="\/download\/CodeRemoteManager-1\.0\.0-source\.zip"/);
  assert.match(pages.get("download"), /npm\.cmd ci --ignore-scripts.*config\.example\.json.*config\.local\.json.*npm\.cmd run safe:install/s);
  assert.match(pages.get("download"), /<details class="advanced-details">.*release-manifest\.json.*<\/details>/s);
  assert.match(pages.get("download"), /EXE 없이 직접 설치.*Node\.js.*Tailscale/s);
  assert.match(pages.get("download"), /Windows 1\.0\.0.*public unsigned.*SmartScreen/is);
  assert.match(pages.get("privacy"), /Google ML Kit.*기기 정보.*설치.*식별자.*성능.*API.*이벤트.*오류/s);
  assert.match(pages.get("privacy"), /1\. 운영자와 문의.*MakeCdev.*coderemote@makecdev\.com/s);
  assert.doesNotMatch(pages.get("privacy"), /1\. 적용 범위와 개발자/);
  for (const route of ["", "download", "terms", "support", "delete-account", "licenses"]) {
    const visibleCopy = pages.get(route).replace(/<[^>]+>/g, " ").replace(/\S*makecdev\.com\S*/gi, "");
    assert.doesNotMatch(visibleCopy, /MakeCdev/i, route);
  }
  assert.match(pages.get("privacy"), /free to download.*one execution account.*one-time non-consumable Pro purchase.*five execution accounts/is);
  assert.match(pages.get("terms"), /무료로 다운로드.*원격 기능.*실행 계정 1개.*Pro.*최대 5개/s);
  assert.match(pages.get("terms"), /pro_multi_account.*현지 가격.*구매 복원/s);
  assert.match(pages.get("support"), /coderemote@makecdev\.com/);
  assert.match(pages.get("delete-account"), /별도 회원 계정.*제공하지 않/s);
  assert.match(pages.get("delete-account"), /Codex.*OpenAI.*Tailscale/s);
  for (const route of ["privacy", "terms", "support", "delete-account"]) {
    assert.match(pages.get(route), /<time datetime="2026-08-02">/, route);
  }
  assert.match(pages.get("licenses"), /<time datetime="2026-08-03">/);
  assert.doesNotMatch(pages.get(""), /<time datetime=/);
  assert.match(pages.get("licenses"), /Lucide Icons and Contributors.*ISC License/s);
  assert.match(pages.get("licenses"), /Feather.*Cole Bemis.*MIT License/s);
  assert.doesNotMatch(pages.get("licenses"), /Lucide.*AppWindow/i);
});

test("public site exposes the bilingual Pro policy contract", async (t) => {
  const pages = await buildPublicPages(t);

  await t.test("home describes the free and Pro account boundary without a subscription claim", () => {
    const home = pages.get("");
    assert.match(home, /무료 다운로드.*원격 기능.*계정 1개.*Pro.*계정 5개/s);
    assert.match(home, /free to download.*remote features.*one account.*Pro.*five accounts/is);
    assert.doesNotMatch(home, /구독(?:을 제공합니다|입니다)|구독형 상품(?:입니다|을 제공합니다)|자동 갱신됩니다|subscription (?:is|will be) (?:available|included)|auto-renews?/i);
  });

  await t.test("terms describe purchase, restore, refund, grace, and service sunset protection", () => {
    const terms = pages.get("terms");
    assert.match(terms, /pro_multi_account/i);
    assert.match(terms, /비소모성.*일회성 구매.*Google Play.*구매 복원/s);
    assert.match(terms, /one-time non-consumable purchase.*Google Play.*restore/is);
    assert.match(terms, /환불.*법률상.*권리.*7일.*유예.*서비스 종료.*기존 구매자/s);
    assert.match(terms, /refund.*applicable law.*seven-day grace.*service.*existing purchasers/is);
    assert.match(terms, /(?:취소[^.\n]{0,80}환불|환불[^.\n]{0,80}취소)[^.\n]{0,120}Pro[^.\n]{0,80}(?:종료|회수|비활성)/);
    assert.match(terms, /(?:cancel(?:lation|led|s)?[^.\n]{0,80}refund|refund[^.\n]{0,80}cancel(?:lation|led|s)?)[^.\n]{0,120}Pro access[^.\n]{0,80}(?:ends?|revok|deactivat)/i);
  });

  await t.test("privacy describes transient verification and the minimal processor record", () => {
    const privacy = pages.get("privacy");
    assert.match(privacy, /구매 토큰.*일시적.*검증.*SHA-256.*설치 공개키 지문/s);
    assert.match(privacy, /purchase token.*transient.*verif.*SHA-256.*installation public-key thumbprint/is);
    assert.match(privacy, /Google Play.*Cloud Run.*Firestore.*Pub\/Sub/s);
    assert.match(privacy, /Pro[^.\n]{0,80}(?:제공|활성화)[^.\n]{0,80}(?:부정 사용|사기)[^.\n]{0,40}(?:방지|예방)/);
    assert.match(privacy, /(?:provide|activate) Pro[^.\n]{0,100}prevent (?:fraud|abuse)/i);
    assert.match(privacy, /구매 기록[^.\n]{0,100}보존/);
    assert.match(privacy, /purchase record[^.\n]{0,100}retain/i);
    assert.match(privacy, /원본 구매 토큰[^.\n]{0,100}저장하지 않/);
    assert.match(privacy, /does not (?:retain|store)[^.\n]{0,80}raw purchase token/i);
    assert.match(privacy, /(?:Google 이메일[^.\n]{0,60}결제수단|결제수단[^.\n]{0,60}Google 이메일)[^.\n]{0,80}수집하지 않/);
    assert.match(privacy, /does not collect (?:Google (?:account )?email(?: addresses?)?[^.\n]{0,40}(?:payment methods?|payment-method details?)|(?:payment methods?|payment-method details?)[^.\n]{0,40}Google (?:account )?email(?: addresses?)?)/i);
  });

  await t.test("support and deletion routes cover refunds, support, and no Code Remote login", () => {
    assert.match(pages.get("support"), /Google Play.*환불.*coderemote@makecdev\.com/s);
    assert.match(pages.get("support"), /Google Play.*refund.*coderemote@makecdev\.com/is);
    assert.match(pages.get("delete-account"), /Code Remote Manager(?:는|에는)[^.\n]{0,120}(?:로그인|회원 계정)[^.\n]{0,80}(?:없|제공하지 않)/);
    assert.match(pages.get("delete-account"), /(?:Code Remote Manager|The app) does not (?:create|provide|require) (?:a |an |its own )?(?:Code Remote )?(?:login|membership|sign-up account)/i);
  });

  await t.test("every public policy route uses the August 2 effective date and canonical links", () => {
    for (const route of ["privacy", "terms", "support", "delete-account"]) {
      const html = pages.get(route);
      assert.match(html, /<time datetime="2026-08-02">/, route);
      assert.match(html, new RegExp(`<link rel="canonical" href="https://coderemotemanager\\.makecdev\\.com/${route}">`), `${route}:canonical`);
      for (const link of ["privacy", "terms", "support", "delete-account"]) {
        assert.match(html, new RegExp(`href="/${link}"`), `${route}:${link}`);
      }
    }
  });
});

test("Firebase Hosting config deploys only the generated public site to coderemotemanager", async () => {
  const [firebase, project] = await Promise.all([
    readFile(new URL("../firebase.json", import.meta.url), "utf8"),
    readFile(new URL("../.firebaserc", import.meta.url), "utf8"),
  ]);
  const hosting = JSON.parse(firebase).hosting;
  const firebaseProject = JSON.parse(project);

  assert.equal(hosting.target, "coderemotemanager");
  assert.equal(hosting.public, "website/dist");
  assert.equal(hosting.cleanUrls, true);
  assert.deepEqual(firebaseProject.projects, { default: "makecdev-web" });
  assert.deepEqual(
    firebaseProject.targets["makecdev-web"].hosting.coderemotemanager,
    ["coderemotemanager"],
  );
});

test("public download staging wraps the verified Windows installer in a ZIP", async (t) => {
  const temporary = await mkdtemp(join(tmpdir(), "crm-public-download-"));
  t.after(() => rm(temporary, { recursive: true, force: true }));
  const candidate = join(temporary, "candidate");
  const output = join(temporary, "site");
  await mkdir(candidate, { recursive: true });
  await mkdir(join(output, "download"), { recursive: true });
  const installer = Buffer.from("verified-windows-installer");
  const source = Buffer.from("verified-release-source");
  const sha256 = (value) => createHash("sha256").update(value).digest("hex");
  await Promise.all([
    writeFile(join(candidate, "CodeRemoteManagerSetup.exe"), installer),
    writeFile(join(candidate, "CodeRemoteManager-1.0.0-source.zip"), source),
    writeFile(join(output, "download", "index.html"), "download-guide"),
    writeFile(join(output, "download", "CodeRemoteManagerSetup.exe"), "stale-raw-installer"),
    writeFile(join(candidate, "release-manifest.json"), JSON.stringify({
      schemaVersion: 2,
      product: "Code Remote Manager",
      version: "1.0.0",
      distribution: { windows: { channel: "public-unsigned", signature: "unsigned" } },
      artifacts: {
        windows: {
          file: "CodeRemoteManagerSetup.exe",
          sha256: sha256(installer),
          bytes: installer.length,
          signing: "unsigned",
        },
        source: {
          file: "CodeRemoteManager-1.0.0-source.zip",
          sha256: sha256(source),
          bytes: source.length,
          signing: "not-applicable",
        },
      },
    })),
  ]);

  const result = spawnSync(process.execPath, ["tools/stage-public-download.mjs", candidate, output], {
    cwd: root,
    encoding: "utf8",
  });
  assert.equal(result.status, 0, result.stderr || result.stdout);
  const extracted = join(temporary, "extracted");
  await mkdir(extracted);
  const extractResult = spawnSync("tar.exe", [
    "-xf",
    join(output, "download", "CodeRemoteManagerSetup.zip"),
    "-C",
    extracted,
  ], { encoding: "utf8" });
  assert.equal(extractResult.status, 0, extractResult.stderr || extractResult.stdout);
  assert.deepEqual(
    await Promise.all([
      readFile(join(extracted, "CodeRemoteManagerSetup.exe")),
      readFile(join(output, "download", "CodeRemoteManager-1.0.0-source.zip")),
    ]),
    [installer, source],
  );
  await assert.rejects(
    readFile(join(output, "download", "CodeRemoteManagerSetup.exe")),
    { code: "ENOENT" },
  );
  assert.equal(await readFile(join(output, "download", "index.html"), "utf8"), "download-guide");
  assert.deepEqual(
    JSON.parse(await readFile(join(output, "download", "release-manifest.json"), "utf8")),
    JSON.parse(await readFile(join(candidate, "release-manifest.json"), "utf8")),
  );
});

test("public site styles keep mobile controls touchable and content responsive", async () => {
  const css = await readFile(new URL("../website/assets/site.css", import.meta.url), "utf8");

  assert.match(css, /min-height:\s*48px/);
  assert.match(css, /@media\s*\(max-width:\s*640px\)/);
  assert.match(css, /overflow-wrap:\s*anywhere/);
  assert.match(css, /\.brand::before\s*{[^}]*content:\s*"C"[^}]*color:\s*var\(--accent\)/s);
});
