/**
 * 「只有一份」的機械證明。
 *
 * leo 的驗收條件：「改完之後，`grep` 得出『決定用哪些資源』的邏輯**只有一個地方**。
 * 兩個以上呼叫端各自有一份 ⇒ 不算完成。」
 *
 * 這份測試就是把那個 grep 寫成會紅的東西：
 *   ① 規則的每一支函式，全 repo 只有 `shared/resource-rule/` 有實作
 *      （`cli/src/lib/resource-rule/` 是它的逐位元組鏡射，由 sync 腳本產生並看守，不算第二份）
 *   ② 鏡射與原稿逐位元組相同（sync --check 的同一道閘，這裡再測一次讓 `npm test` 也擋得住）
 *   ③ 共用層不准長出依賴——有依賴就會有某條路吃不到它
 */

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync, readdirSync, statSync } from 'node:fs';
import { join, relative } from 'node:path';
import { fileURLToPath } from 'node:url';
import { createHash } from 'node:crypto';

const REPO = join(fileURLToPath(new URL('.', import.meta.url)), '..', '..');
const SOURCE_DIR = join(REPO, 'shared/resource-rule');
const MIRROR_DIR = join(REPO, 'cli/src/lib/resource-rule');

/** 規則的實作特徵：這些**宣告**只准出現在原稿目錄（與它的鏡射）裡。 */
const RULE_DECLARATIONS = [
  'function planResources',
  'function applyResourcePlan',
  'function shareSameResource',
  'function parseWranglerRequirements',
  'function normalizeLiveBindings',
  'function normalizeLiveVars',
  'function createCloudflareResourceApi',
];

const SKIP_DIRS = new Set([
  'node_modules', '.git', 'dist', '.wrangler', '.worker-builds', '.component-builds',
  '.github-public', 'coverage',
]);

/** 只掃「人會寫程式的地方」；產生物與二進位不掃。 */
function walk(dir: string, out: string[] = []): string[] {
  for (const name of readdirSync(dir)) {
    if (SKIP_DIRS.has(name)) continue;
    const abs = join(dir, name);
    const st = statSync(abs);
    if (st.isDirectory()) walk(abs, out);
    else if (/\.(ts|tsx|js|mjs|cjs)$/.test(name)) out.push(abs);
  }
  return out;
}

const sha256 = (b: Buffer): string => createHash('sha256').update(b).digest('hex');

test('① 規則的實作全 repo 只有一份（原稿目錄 + 它的鏡射，沒有第三處）', () => {
  const files = walk(REPO);
  const offenders: string[] = [];

  for (const abs of files) {
    const rel = relative(REPO, abs);
    // 原稿與鏡射本來就該有；測試檔在講規則、不是實作規則
    if (rel.startsWith('shared/resource-rule/')) continue;
    if (rel.startsWith('cli/src/lib/resource-rule/')) continue;
    if (rel.startsWith('cli/tests/')) continue;
    if (rel === 'scripts/sync-resource-rule.mjs') continue;

    const src = readFileSync(abs, 'utf8');
    for (const decl of RULE_DECLARATIONS) {
      if (src.includes(decl)) offenders.push(`${rel} → ${decl}`);
    }
  }

  assert.deepEqual(offenders, [],
    '「決定用哪些資源」的實作出現在共用層之外——這正是本票要消滅的東西：\n' +
    offenders.map((o) => `  • ${o}`).join('\n') +
    '\n要改規則就改 shared/resource-rule/，呼叫端只准 import。');

  console.log(`\n  ① 掃過 ${files.length} 個原始碼檔，${RULE_DECLARATIONS.length} 支規則函式的實作` +
    ' 全部只出現在 shared/resource-rule/（＋機械鏡射）');
});

test('② CLI 帶的那份與原稿逐位元組相同（漂移＝第二份實作偷偷長出來）', () => {
  const files = readdirSync(SOURCE_DIR).filter((f) => f.endsWith('.mjs')).sort();
  assert.ok(files.length > 0, 'shared/resource-rule/ 裡沒有任何 .mjs 原稿');

  const mirrored = readdirSync(MIRROR_DIR).filter((f) => f.endsWith('.mjs')).sort();
  assert.deepEqual(mirrored, files, '鏡射目錄的檔案清單與原稿不一致');

  for (const f of files) {
    const a = sha256(readFileSync(join(SOURCE_DIR, f)));
    const b = sha256(readFileSync(join(MIRROR_DIR, f)));
    assert.equal(b, a, `cli/src/lib/resource-rule/${f} 與原稿不一致——不要手改產生物，` +
      '改 shared/resource-rule/ 後跑 node scripts/sync-resource-rule.mjs');
    console.log(`  ② ${f.padEnd(24)} sha256 ${a.slice(0, 16)}  原稿 = 鏡射`);
  }
});

test('③ 共用層零外部依賴（只准 import 同目錄的兄弟檔）', () => {
  for (const f of readdirSync(SOURCE_DIR).filter((x) => x.endsWith('.mjs'))) {
    const src = readFileSync(join(SOURCE_DIR, f), 'utf8');
    const imports = [...src.matchAll(/^\s*import\s[^'"]*['"]([^'"]+)['"]/gm)].map((m) => m[1]);
    for (const spec of imports) {
      assert.ok(spec.startsWith('./'),
        `shared/resource-rule/${f} import 了 "${spec}"——共用層一旦有外部依賴，` +
        '就會有某條路（Workers runtime／安裝器）吃不到它。');
    }
    assert.doesNotMatch(src, /require\(|from\s+['"]node:/,
      `shared/resource-rule/${f} 用到 node 專屬 API——Cloudflare Workers 上跑不起來。`);
    console.log(`  ③ ${f.padEnd(24)} import: ${imports.length ? imports.join(', ') : '（無）'}`);
  }
});
