import { readFileSync } from 'node:fs';
import { join } from 'node:path';

/**
 * 🔴 路由遮蔽 —— `xxx:verb` 风格的路径会把同前缀的字面量路由**整个吞掉**。
 *
 * Express 把 `:id/script:regenerate` 里的 `:regenerate` 当**路径参数**,
 * 模式实际是「字面量 `script` + 参数」。于是:
 *   POST /plans/x/script-feedback  → 命中 script:regenerate(参数 = `-feedback`)
 *   POST /plans/x/script-copy      → 同上(参数 = `-copy`)
 *
 * 实测(2026-08-04):话术 👍👎 反馈按钮**从来没生效过** —— 每点一次实际在重新生成话术
 * (真花 AI 钱 + 覆盖已有稿),而前端拿到 200、界面一切正常,**不会有任何报错**。
 *
 * 治标是声明顺序(字面量在前),治本是把 `:verb` 改成 `/verb`。在改之前,这个 spec
 * 锁住顺序 —— 它是纯文本检查,因为这个 bug 恰恰不会在类型或运行时暴露。
 */
const CONTROLLER = join(
  __dirname,
  '../src/modules/plan-aggregate/plans-aggregate.controller.ts',
);

/** 取出所有 @Post/@Get 装饰器里的路径,按**声明顺序** */
function declaredRoutes(): Array<{ method: string; path: string; at: number }> {
  const src = readFileSync(CONTROLLER, 'utf-8');
  const out: Array<{ method: string; path: string; at: number }> = [];
  const re = /@(Post|Get)\(\s*'([^']+)'\s*\)/g;
  let m: RegExpExecArray | null;
  while ((m = re.exec(src)) !== null) {
    out.push({ method: m[1]!, path: m[2]!, at: m.index });
  }
  return out;
}

/** `a` 会不会把 `b` 吞掉:a 形如 `前缀:参数`,而 b 以同一个前缀开头 */
function shadows(a: string, b: string): boolean {
  const i = a.indexOf(':', a.indexOf('/') + 1); // 跳过开头的 :id
  if (i === -1) return false;
  const literal = a.slice(0, i); // 如 ':id/script'
  if (!literal.includes('/')) return false;
  // b 必须以同一段字面量开头,且后面还有内容(那部分会被当成参数吃掉)
  return b !== a && b.startsWith(literal) && b.length > literal.length && !b.slice(literal.length).startsWith('/');
}

describe('路由遮蔽 —— 字面量路由必须声明在 `:verb` 路由之前', () => {
  test('⭐⭐ 任何被 `:verb` 路由遮蔽的字面量路由,都必须声明得更早', () => {
    const routes = declaredRoutes();
    const problems: string[] = [];
    for (const colonRoute of routes) {
      if (!shadows(colonRoute.path, colonRoute.path)) {
        // colonRoute 自身含 `:verb` 才有遮蔽能力
      }
      for (const literal of routes) {
        if (literal.method !== colonRoute.method) continue;
        if (!shadows(colonRoute.path, literal.path)) continue;
        if (literal.at > colonRoute.at) {
          problems.push(
            `${literal.method} '${literal.path}' 声明在 '${colonRoute.path}' 之后 → 会被它整个吞掉(永远进不来)`,
          );
        }
      }
    }
    expect(problems).toEqual([]);
  });

  test('⭐ 已知的两条:script-feedback / script-copy 都排在 script:regenerate 之前', () => {
    const routes = declaredRoutes();
    const at = (p: string) => routes.find((r) => r.path === p)?.at ?? -1;
    expect(at(':id/script-feedback')).toBeGreaterThan(-1);
    expect(at(':id/script-copy')).toBeGreaterThan(-1);
    expect(at(':id/script-feedback')).toBeLessThan(at(':id/script:regenerate'));
    expect(at(':id/script-copy')).toBeLessThan(at(':id/script:regenerate'));
  });
});
