/**
 * deploy-code-component.test.ts — 離線驗「code 零件接進部署流程」（Arcrun#4 後續）。
 *
 * 驗三件事（不打網路、不部署）：
 *   1. 部署清單含 code：discoverWorkerDirs 在產物齊全時把 registry/components/code 收進 tier1；
 *      缺 vendored quickjs.wasm 時誠實跳過（gate 精神比照 tier1 component.wasm）。
 *   2. 產物完整：repo 內 code 零件的部署必要檔存在且 quickjs.wasm 已被 git 追蹤
 *      （= 會進 Gitea archive tarball，acr update 才拿得到）；共享依賴 SHARED_DEPLOY_DEPS
 *      涵蓋 code 零件 package.json 的全部 runtime deps（drift 守門）。
 *   3. 注入正確：stripOfficialOnlyBindings 對 code 的 wrangler.toml 剝掉官方 route
 *      （code.arcrun.dev），保留 workers_dev / COMPONENT_ID → self-hosted 落自己帳號的 workers.dev。
 *
 * 用 Node 內建 test runner（node --test，零額外依賴；Node ≥22.18 自帶 TS type-stripping）。
 */
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, statSync } from 'node:fs';
import { execFileSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import { tmpdir } from 'node:os';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import {
  discoverWorkerDirs,
  SELF_CONTAINED_COMPONENT_WORKERS,
  SHARED_DEPLOY_DEPS,
  stripOfficialOnlyBindings,
  injectMultiTenant,
} from '../src/lib/deploy.ts';

const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
const CODE_DIR = join(REPO_ROOT, 'registry', 'components', 'code');

// ── 1. 部署清單 ───────────────────────────────────────────────────────────────

function makeFixtureRoot(opts: { withWasm: boolean; withToml?: boolean }): string {
  const root = mkdtempSync(join(tmpdir(), 'arcrun-test-root-'));
  const dir = join(root, 'registry', 'components', 'code');
  mkdirSync(join(dir, 'vendor'), { recursive: true });
  if (opts.withToml !== false) writeFileSync(join(dir, 'wrangler.toml'), 'name = "arcrun-code"\n');
  if (opts.withWasm) writeFileSync(join(dir, 'vendor', 'quickjs.wasm'), 'fake-wasm');
  return root;
}

test('discoverWorkerDirs：code 產物齊全 → 進 tier1（部署清單含 code）', () => {
  const root = makeFixtureRoot({ withWasm: true });
  const { tier1 } = discoverWorkerDirs(root);
  assert.ok(tier1.some(d => d.endsWith(join('registry', 'components', 'code'))),
    `tier1 應含 code 目錄，實得：${JSON.stringify(tier1)}`);
});

test('discoverWorkerDirs：缺 vendor/quickjs.wasm → 誠實跳過（不讓 wrangler 因缺檔失敗）', () => {
  const root = makeFixtureRoot({ withWasm: false });
  const { tier1, tier2 } = discoverWorkerDirs(root);
  assert.ok(!tier1.concat(tier2).some(d => d.includes('code')),
    'wasm 缺席時不應把 code 排進部署清單');
});

test('discoverWorkerDirs：缺 wrangler.toml → 跳過', () => {
  const root = makeFixtureRoot({ withWasm: true, withToml: false });
  const { tier1 } = discoverWorkerDirs(root);
  assert.equal(tier1.length, 0);
});

test('SELF_CONTAINED_COMPONENT_WORKERS 宣告 code 目錄與必要產物', () => {
  const code = SELF_CONTAINED_COMPONENT_WORKERS.find(w => w.dir.join('/') === 'registry/components/code');
  assert.ok(code, '清單應含 registry/components/code');
  assert.ok(code!.requires.some(r => r.join('/') === 'vendor/quickjs.wasm'));
});

// ── 2. 產物完整（對 repo 實體檢查） ──────────────────────────────────────────

test('repo 內 code 零件部署必要檔齊全，quickjs.wasm 已 commit（會進 Gitea archive）', () => {
  for (const f of ['wrangler.toml', 'index.ts', 'sandbox.mjs', join('vendor', 'quickjs.wasm')]) {
    assert.ok(statSync(join(CODE_DIR, f)).isFile(), `缺 ${f}`);
  }
  assert.ok(statSync(join(CODE_DIR, 'vendor', 'quickjs.wasm')).size > 100_000,
    'quickjs.wasm 尺寸異常（應約 491KB）');
  // git 追蹤 = 會被 git archive 打包進 Gitea 下載物（.gitignore 放行是否生效的最終證據）。
  const tracked = execFileSync('git', ['ls-files', '--', 'registry/components/code/vendor/quickjs.wasm'],
    { cwd: REPO_ROOT, encoding: 'utf8' }).trim();
  assert.equal(tracked, 'registry/components/code/vendor/quickjs.wasm',
    'vendor/quickjs.wasm 未被 git 追蹤（.gitignore 放行失效或忘了 git add）');
});

// vendored quickjs.wasm 的內容指紋（@jitl/quickjs-wasmfile-release-sync ^0.32.0 的
// emscripten-module.wasm）。升套件版本卻沒重跑 vendor（npm install → postinstall）再 commit
// → 這裡紅燈，防「package.json 升了、部署物 wasm 還是舊版」的 drift（審查小記 2026-07-07）。
// 升版 SOP：cd registry/components/code && npm install → commit vendor/quickjs.wasm → 更新此常數。
const QUICKJS_WASM_SHA256 = '105c3bed22d457e43e3d1c3c1c6959fda62a8fe06f0fc8a985303c3a2be72232';

test('vendored quickjs.wasm 內容指紋一致（升版沒重 vendor 就紅燈）', () => {
  const buf = readFileSync(join(CODE_DIR, 'vendor', 'quickjs.wasm'));
  const sha = createHash('sha256').update(buf).digest('hex');
  assert.equal(sha, QUICKJS_WASM_SHA256,
    'vendor/quickjs.wasm 指紋與紀錄不符：若剛升 quickjs 套件版本，重跑 npm install 讓 postinstall ' +
    '重 vendor、commit 新 wasm，並同步更新 QUICKJS_WASM_SHA256');
});

test('SHARED_DEPLOY_DEPS 涵蓋 code 零件全部 runtime deps（drift 守門）', () => {
  const pkg = JSON.parse(readFileSync(join(CODE_DIR, 'package.json'), 'utf8')) as
    { dependencies?: Record<string, string> };
  for (const [dep, ver] of Object.entries(pkg.dependencies ?? {})) {
    assert.ok(dep in SHARED_DEPLOY_DEPS,
      `code 零件 runtime dep「${dep}」不在 SHARED_DEPLOY_DEPS → 共享安裝路徑下 esbuild 會解析失敗`);
    assert.equal(SHARED_DEPLOY_DEPS[dep], ver,
      `「${dep}」版本 drift：SHARED_DEPLOY_DEPS=${SHARED_DEPLOY_DEPS[dep]} vs 零件 package.json=${ver}`);
  }
});

// ── 3. 注入正確（用 repo 內真 wrangler.toml） ────────────────────────────────

test('stripOfficialOnlyBindings：剝掉 code.arcrun.dev route，保留 workers_dev + COMPONENT_ID', () => {
  const toml = readFileSync(join(CODE_DIR, 'wrangler.toml'), 'utf8');
  assert.match(toml, /code\.arcrun\.dev/, '前提：repo toml 應含官方 route（官方 CI 部署用）');
  const out = stripOfficialOnlyBindings(toml);
  assert.ok(!/\[\[routes\]\]|zone_name|code\.arcrun\.dev/.test(out), `官方 route 應被剝除：\n${out}`);
  assert.match(out, /workers_dev\s*=\s*true/, 'workers_dev 須保留（self-hosted 靠它對外）');
  assert.match(out, /COMPONENT_ID\s*=\s*"code"/, '[vars] COMPONENT_ID 須保留');
  assert.match(out, /name\s*=\s*"arcrun-code"/, 'worker 名須保留（cypher wasmWorkerUrl 慣例 arcrun-{kebab}）');
});

test('injectMultiTenant：code toml 有 [vars] → 插入 MULTI_TENANT="false"（無害，與其他 worker 一致）', () => {
  const toml = readFileSync(join(CODE_DIR, 'wrangler.toml'), 'utf8');
  const out = injectMultiTenant(toml);
  assert.match(out, /MULTI_TENANT\s*=\s*"false"/);
});

// ── 4. acr parts 清單含 code ─────────────────────────────────────────────────
// （parts.ts 內部以 .js 副檔名 import 相鄰模組，node --test 的 type-stripping 不重寫
//   副檔名 → 無法直接 import；以原始碼文字驗證清單含 code 條目。tsc 另保證型別正確。）

test('BUILTIN_COMPONENTS（acr parts 靜態清單）含 code 條目', () => {
  const src = readFileSync(join(REPO_ROOT, 'cli', 'src', 'commands', 'parts.ts'), 'utf8');
  assert.match(src, /canonical_id: 'code'/, 'parts.ts BUILTIN_COMPONENTS 應含 code');
  assert.match(src, /component: code/, 'code 條目應含 config_example（component: code）');
});
