/**
 * 一次性补齐 @Sensitive 装饰器
 *
 * 复用 audit-sensitive-gap 的解析与启发式，定位每个缺口端点并插入 @Sensitive()。
 * 同时确保 import 行包含 Sensitive。
 *
 * 用法：
 *   npx ts-node testing/scripts/audit-sensitive-patch.ts        # dry-run
 *   npx ts-node testing/scripts/audit-sensitive-patch.ts --apply # 写盘
 */

import * as fs from 'fs';
import * as path from 'path';

const REPO_ROOT = path.resolve(__dirname, '../..');
const BACKEND_SRC = path.join(REPO_ROOT, 'backend/src');
const APPLY = process.argv.includes('--apply');

type Verb = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';

const SENSITIVE_PATTERNS: Array<{ name: string; re: RegExp }> = [
  { name: 'auth/credential', re: /password|reset|token|secret|credential|api[-_]?key/i },
  { name: 'bulk/batch', re: /\b(bulk|batch|mass|all)\b/i },
  { name: 'permission/role', re: /\brole\b|\bpermission\b|data[-_]?scope|\bgrant\b|\brevoke\b|\baccess\b/i },
  { name: 'lifecycle', re: /\block\b|unlock|disable|enable|suspend|activate|archive|restore|deactivate/i },
  { name: 'approval/state', re: /\bapprove\b|\breject\b|\bpublish\b|\btransfer\b|\bmerge\b|\bemergency\b|\bbypass\b|impersonate|sudo/i },
];

function findControllerFiles(dir: string): string[] {
  const out: string[] = [];
  const stack = [dir];
  while (stack.length) {
    const d = stack.pop()!;
    for (const e of fs.readdirSync(d, { withFileTypes: true })) {
      const p = path.join(d, e.name);
      if (e.isDirectory()) stack.push(p);
      else if (e.isFile() && p.endsWith('.controller.ts')) out.push(p);
    }
  }
  return out;
}

interface Gap {
  file: string;
  verb: Verb;
  fullPath: string;
  methodName: string;
  auditableLineIdx: number;
  reasons: string[];
}

function analyzeFile(file: string): Gap[] {
  const src = fs.readFileSync(file, 'utf8');
  const lines = src.split('\n');

  let controllerPrefix = '';
  for (let i = 0; i < lines.length; i++) {
    const m = lines[i].match(/^@Controller\(\s*(?:'([^']*)'|"([^"]*)"|`([^`]*)`)?\s*\)/);
    if (m) {
      controllerPrefix = (m[1] ?? m[2] ?? m[3] ?? '').replace(/^\/+|\/+$/g, '');
      break;
    }
  }

  const gaps: Gap[] = [];
  for (let i = 0; i < lines.length; i++) {
    if (!/^\s*@Auditable\(/.test(lines[i])) continue;

    let verb: Verb | null = null;
    let subPath = '';
    for (let j = i - 1; j >= Math.max(0, i - 8); j--) {
      const m = lines[j].match(/^\s*@(Get|Post|Put|Patch|Delete)\(\s*(?:'([^']*)'|"([^"]*)"|`([^`]*)`)?/);
      if (m) {
        verb = m[1].toUpperCase() as Verb;
        subPath = (m[2] ?? m[3] ?? m[4] ?? '').replace(/^\/+/, '');
        break;
      }
    }
    if (!verb) continue;

    let methodName = '?';
    let isSensitive = false;
    for (let k = i + 1; k < Math.min(lines.length, i + 20); k++) {
      if (/^\s*@Sensitive\(/.test(lines[k])) isSensitive = true;
      const mn = lines[k].match(/^\s*(?:async\s+)?(\w+)\s*\(/);
      if (mn && !lines[k].trim().startsWith('@')) {
        methodName = mn[1];
        break;
      }
    }
    if (isSensitive) continue;

    const prefix = controllerPrefix ? '/' + controllerPrefix : '';
    const tail = subPath ? '/' + subPath : '';
    const fullPath = prefix + tail || '/';

    const reasons: string[] = [];
    if (verb === 'DELETE') reasons.push('verb=DELETE');
    const haystack = `${fullPath} ${methodName}`;
    for (const p of SENSITIVE_PATTERNS) {
      if (p.re.test(haystack)) reasons.push(p.name);
    }
    if (reasons.length === 0) continue;

    gaps.push({
      file,
      verb,
      fullPath,
      methodName,
      auditableLineIdx: i,
      reasons,
    });
  }
  return gaps;
}

function ensureSensitiveImport(lines: string[]): { changed: boolean; lines: string[] } {
  // Find the import line for the auditable decorator module
  for (let i = 0; i < lines.length; i++) {
    const m = lines[i].match(/from ['"][^'"]*\/decorators\/auditable\.decorator['"]/);
    if (!m) continue;
    if (/Sensitive/.test(lines[i])) return { changed: false, lines };

    // Add Sensitive into the existing import braces
    const newLine = lines[i].replace(/import\s*\{\s*([^}]*)\}/, (_, inner) => {
      const items = inner
        .split(',')
        .map((s: string) => s.trim())
        .filter(Boolean);
      if (!items.includes('Sensitive')) items.push('Sensitive');
      return `import { ${items.join(', ')} }`;
    });
    if (newLine !== lines[i]) {
      lines[i] = newLine;
      return { changed: true, lines };
    }
  }
  // Fallback: prepend an import line near the top (after last import)
  let lastImport = -1;
  for (let i = 0; i < lines.length; i++) {
    if (/^import\s/.test(lines[i])) lastImport = i;
  }
  if (lastImport >= 0) {
    lines.splice(
      lastImport + 1,
      0,
      `import { Sensitive } from '@core/observability/audit/decorators/auditable.decorator';`,
    );
    return { changed: true, lines };
  }
  return { changed: false, lines };
}

function applyPatchToFile(file: string, gaps: Gap[]): { added: number; importChanged: boolean } {
  let lines = fs.readFileSync(file, 'utf8').split('\n');

  // Sort descending by line index so insertions don't shift other indexes
  const sorted = gaps.slice().sort((a, b) => b.auditableLineIdx - a.auditableLineIdx);
  let added = 0;
  for (const g of sorted) {
    const auditableLine = lines[g.auditableLineIdx];
    const indent = auditableLine.match(/^\s*/)![0];
    lines.splice(g.auditableLineIdx + 1, 0, `${indent}@Sensitive()`);
    added++;
  }

  let importChanged = false;
  if (added > 0) {
    const result = ensureSensitiveImport(lines);
    importChanged = result.changed;
    lines = result.lines;
  }

  if (APPLY && added > 0) fs.writeFileSync(file, lines.join('\n'));
  return { added, importChanged };
}

function main() {
  const files = findControllerFiles(BACKEND_SRC);
  let totalGaps = 0;
  let totalImports = 0;
  const fileSummary: Array<{ file: string; gaps: number; importChanged: boolean }> = [];

  for (const f of files) {
    const gaps = analyzeFile(f);
    if (gaps.length === 0) continue;
    const { added, importChanged } = applyPatchToFile(f, gaps);
    totalGaps += added;
    if (importChanged) totalImports++;
    fileSummary.push({ file: path.relative(REPO_ROOT, f), gaps: added, importChanged });
  }

  console.log(`Mode: ${APPLY ? 'APPLY' : 'DRY-RUN'}\n`);
  console.log(`Files patched: ${fileSummary.length}`);
  console.log(`@Sensitive added: ${totalGaps}`);
  console.log(`Imports updated: ${totalImports}\n`);
  for (const s of fileSummary) {
    console.log(`  ${s.file}  +${s.gaps}${s.importChanged ? '  [import]' : ''}`);
  }
  if (!APPLY) console.log('\n(dry-run; rerun with --apply to write changes)');
}

main();
