/**
 * 权限矩阵校验脚本
 *
 * 从 Controller 提取每个端点的 @RequirePermissions，
 * 从种子数据提取每个角色的权限，交叉验证是否有权限配置缺失或过度授权。
 *
 * 用法: npx ts-node testing/scripts/permission-matrix-check.ts
 */

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

// ==================== 类型定义 ====================

interface EndpointInfo {
  controller: string;
  method: string; // GET, POST, PUT, DELETE, PATCH
  route: string; // 完整路由
  permissionKey: string; // PERFORMANCE_PERMISSIONS.XXX
  permissionValue: string; // 解析后的权限码
  methodName: string; // 方法名
}

interface RolePermissions {
  name: string;
  code: string;
  permissions: string[];
}

type Severity = 'error' | 'warning' | 'info';

interface CheckResult {
  severity: Severity;
  role: string;
  endpoint: EndpointInfo;
  message: string;
  suggestion: string;
}

// ==================== 路径常量 ====================

const ROOT_DIR = path.resolve(__dirname, '..', '..');
const CONTROLLERS_DIR = path.join(
  ROOT_DIR,
  'backend/src/modules/performance/controllers',
);
const PERMISSIONS_FILE = path.join(
  ROOT_DIR,
  'backend/src/modules/performance/constants/permissions.ts',
);
const ROLES_SEED_FILE = path.join(
  ROOT_DIR,
  'backend/prisma/seeds/roles.seed.ts',
);

// ==================== 1. 解析 PERFORMANCE_PERMISSIONS 常量 ====================

function parsePermissionsConstants(): Record<string, string> {
  const content = fs.readFileSync(PERMISSIONS_FILE, 'utf-8');
  const result: Record<string, string> = {};

  // 匹配形如: KEY: 'value', 或 KEY: "value",
  const regex = /(\w+)\s*:\s*['"]([^'"]+)['"]/g;
  let match: RegExpExecArray | null;
  while ((match = regex.exec(content)) !== null) {
    result[match[1]] = match[2];
  }

  return result;
}

// ==================== 2. 从 Controller 提取端点信息 ====================

function extractEndpoints(
  permConsts: Record<string, string>,
): EndpointInfo[] {
  const endpoints: EndpointInfo[] = [];
  const controllerFiles = fs
    .readdirSync(CONTROLLERS_DIR)
    .filter((f) => f.endsWith('.controller.ts'));

  for (const file of controllerFiles) {
    const filePath = path.join(CONTROLLERS_DIR, file);
    const content = fs.readFileSync(filePath, 'utf-8');

    // 提取控制器路由前缀
    const controllerMatch = content.match(
      /@Controller\(\s*['"]([^'"]+)['"]\s*\)/,
    );
    if (!controllerMatch) continue;
    const controllerPrefix = controllerMatch[1];

    // 将文件按方法分块：找到每个方法装饰器组
    // 策略：找所有 HTTP 方法装饰器，然后向上查找 @RequirePermissions
    const lines = content.split('\n');

    let i = 0;
    while (i < lines.length) {
      const line = lines[i];

      // 检查是否是 HTTP 方法装饰器行
      const httpMatch = line.match(
        /@(Get|Post|Put|Delete|Patch)\(\s*(?:['"]([^'"]*)['"]\s*)?\)/,
      );
      if (httpMatch) {
        const httpMethod = httpMatch[1].toUpperCase();
        const routePath = httpMatch[2] || '';

        let permissionKey = '';
        let permissionValue = '';

        // 向上搜索装饰器块（直到遇到空行、闭括号 } 或另一个方法声明）
        // 装饰器紧贴方法定义，所以从 HTTP 装饰器往上找即可
        for (let j = i - 1; j >= Math.max(0, i - 5); j--) {
          const trimmed = lines[j].trim();
          // 遇到空行、闭括号、方法体开始说明超出当前装饰器块
          if (trimmed === '' || trimmed === '}' || trimmed.match(/^\w/)) break;
          const permMatch = trimmed.match(
            /@RequirePermissions\(\s*PERFORMANCE_PERMISSIONS\.(\w+)\s*\)/,
          );
          if (permMatch) {
            permissionKey = permMatch[1];
            permissionValue = permConsts[permissionKey] || `UNKNOWN:${permissionKey}`;
            break;
          }
        }

        // 也搜索 HTTP 装饰器之后的行（有些 controller 先写 @Get 再写 @RequirePermissions）
        if (!permissionKey) {
          for (let j = i + 1; j < Math.min(lines.length, i + 4); j++) {
            const trimmed = lines[j].trim();
            // 遇到方法声明就停止
            if (trimmed.match(/^(async\s+)?\w+\s*\(/)) break;
            const permMatch = trimmed.match(
              /@RequirePermissions\(\s*PERFORMANCE_PERMISSIONS\.(\w+)\s*\)/,
            );
            if (permMatch) {
              permissionKey = permMatch[1];
              permissionValue = permConsts[permissionKey] || `UNKNOWN:${permissionKey}`;
              break;
            }
          }
        }

        // 找方法名（async methodName 或 methodName）
        let methodName = 'unknown';
        for (let j = i; j < Math.min(lines.length, i + 10); j++) {
          const methodMatch = lines[j].match(
            /(?:async\s+)?(\w+)\s*\(/,
          );
          if (
            methodMatch &&
            !methodMatch[1].match(
              /^(Get|Post|Put|Delete|Patch|RequirePermissions|HttpCode|Controller)$/,
            )
          ) {
            methodName = methodMatch[1];
            break;
          }
        }

        // 构建完整路由
        const fullRoute = routePath
          ? `/${controllerPrefix}/${routePath}`
          : `/${controllerPrefix}`;

        if (permissionKey) {
          endpoints.push({
            controller: file.replace('.controller.ts', ''),
            method: httpMethod,
            route: fullRoute,
            permissionKey,
            permissionValue,
            methodName,
          });
        } else {
          // 没有权限装饰器的端点，记录为无保护端点
          endpoints.push({
            controller: file.replace('.controller.ts', ''),
            method: httpMethod,
            route: fullRoute,
            permissionKey: '(none)',
            permissionValue: '(none)',
            methodName,
          });
        }
      }
      i++;
    }
  }

  return endpoints;
}

// ==================== 3. 从种子数据提取角色权限 ====================

function extractRolePermissions(): RolePermissions[] {
  const content = fs.readFileSync(ROLES_SEED_FILE, 'utf-8');
  const roles: RolePermissions[] = [];

  // 匹配每个角色块
  const roleRegex =
    /\{\s*name:\s*['"]([^'"]+)['"],\s*code:\s*['"]([^'"]+)['"],[\s\S]*?permissions:\s*\[([\s\S]*?)\]\s*,?\s*\}/g;
  let match: RegExpExecArray | null;

  while ((match = roleRegex.exec(content)) !== null) {
    const name = match[1];
    const code = match[2];
    const permBlock = match[3];

    // 提取权限列表
    const perms: string[] = [];
    const permLineRegex = /['"]([^'"]+)['"]/g;
    let permMatch: RegExpExecArray | null;
    while ((permMatch = permLineRegex.exec(permBlock)) !== null) {
      perms.push(permMatch[1]);
    }

    roles.push({ name, code, permissions: perms });
  }

  return roles;
}

// ==================== 4. 业务预期定义 ====================

interface RoleExpectation {
  allowed: string[];
  denied: string[];
}

const ROLE_EXPECTATIONS: Record<string, RoleExpectation> = {
  Employee: {
    allowed: [
      'performance:cycle:view',
      'performance:kpi:view',
      'performance:kpi:evaluate',
      'performance:kpi:self-evaluate',
      'performance:360:view',
      'performance:360:create',
      'performance:360:update',
      'performance:360:submit',
      'performance:result:view:own',
      'performance:grade:view',
    ],
    denied: [
      'performance:cycle:create',
      'performance:cycle:update',
      'performance:cycle:delete',
      'performance:cycle:publish',
      'performance:kpi:manage',
      'performance:kpi:assign',
      'performance:kpi:manager-evaluate',
      'performance:calibration:view',
      'performance:calibration:create',
      'performance:calibration:update',
      'performance:calibration:delete',
      'performance:calibration:adjust',
      'performance:calibration:manage',
      'performance:calibration:participate',
      'performance:result:view',
      'performance:result:view:all',
      'performance:result:calculate',
      'performance:result:publish',
      'performance:report:export',
      'performance:analytics:read',
      'performance:grade:create',
      'performance:grade:update',
      'performance:grade:delete',
      'performance:interview:view',
      'performance:interview:create',
      'performance:interview:update',
      'performance:interview:delete',
      'performance:interview:complete',
      'performance:interview:confirm',
    ],
  },
  Leader: {
    allowed: [
      'performance:cycle:view',
      'performance:kpi:view',
      'performance:kpi:view:team',
      'performance:kpi:evaluate',
      'performance:kpi:self-evaluate',
      'performance:kpi:manager-evaluate',
      'performance:360:view',
      'performance:360:create',
      'performance:360:update',
      'performance:360:submit',
      'performance:result:view:own',
      'performance:grade:view',
    ],
    denied: [
      'performance:cycle:create',
      'performance:cycle:update',
      'performance:cycle:delete',
      'performance:cycle:publish',
      'performance:kpi:manage',
      'performance:kpi:assign',
      'performance:calibration:create',
      'performance:calibration:delete',
      'performance:calibration:adjust',
      'performance:result:calculate',
      'performance:result:publish',
      'performance:report:export',
      'performance:grade:create',
      'performance:grade:update',
      'performance:grade:delete',
    ],
  },
  DepartmentManager: {
    allowed: [
      'performance:cycle:view',
      'performance:kpi:view',
      'performance:kpi:view:team',
      'performance:kpi:evaluate',
      'performance:kpi:self-evaluate',
      'performance:kpi:manager-evaluate',
      'performance:kpi:assign',
      'performance:360:view',
      'performance:360:create',
      'performance:360:submit',
      'performance:calibration:view',
      'performance:calibration:participate',
      'performance:result:view',
      'performance:result:view:own',
      'performance:analytics:read',
      'performance:grade:view',
    ],
    denied: [
      'performance:cycle:create',
      'performance:cycle:update',
      'performance:cycle:delete',
      'performance:cycle:publish',
      'performance:kpi:manage',
      'performance:calibration:create',
      'performance:calibration:delete',
      'performance:result:calculate',
      'performance:result:publish',
      'performance:report:export',
      'performance:grade:create',
      'performance:grade:update',
      'performance:grade:delete',
    ],
  },
};

// ==================== 5. 权限匹配逻辑 ====================

/**
 * 检查角色是否拥有指定权限
 * 支持通配符匹配（如 'performance:*' 匹配所有 performance: 开头的权限）
 */
function roleHasPermission(
  rolePerms: string[],
  requiredPerm: string,
): boolean {
  for (const perm of rolePerms) {
    if (perm === requiredPerm) return true;

    // 通配符匹配
    if (perm.endsWith(':*')) {
      const prefix = perm.slice(0, -1); // 去掉 '*'，保留 ':'
      if (requiredPerm.startsWith(prefix)) return true;
    }

    // 全通配 'module:*'
    if (perm === '*') return true;
  }
  return false;
}

// ==================== 6. 交叉验证 ====================

function crossValidate(
  endpoints: EndpointInfo[],
  roles: RolePermissions[],
): CheckResult[] {
  const results: CheckResult[] = [];

  // 只检查有权限保护的端点
  const protectedEndpoints = endpoints.filter(
    (e) => e.permissionValue !== '(none)',
  );

  for (const [roleCode, expectations] of Object.entries(ROLE_EXPECTATIONS)) {
    const role = roles.find((r) => r.code === roleCode);
    if (!role) {
      results.push({
        severity: 'error',
        role: roleCode,
        endpoint: {} as EndpointInfo,
        message: `角色 ${roleCode} 在种子数据中未找到`,
        suggestion: `在 roles.seed.ts 中添加 ${roleCode} 角色`,
      });
      continue;
    }

    // 检查 allowed 中的权限是否在角色中存在
    for (const perm of expectations.allowed) {
      if (!roleHasPermission(role.permissions, perm)) {
        // 找到使用此权限的端点
        const affectedEndpoints = protectedEndpoints.filter(
          (e) => e.permissionValue === perm,
        );
        if (affectedEndpoints.length > 0) {
          for (const ep of affectedEndpoints) {
            results.push({
              severity: 'error',
              role: roleCode,
              endpoint: ep,
              message: `${role.name} 无法访问 ${ep.method} ${ep.route}`,
              suggestion: `在 roles.seed.ts 中为 ${role.name} 添加 '${perm}'`,
            });
          }
        } else {
          results.push({
            severity: 'warning',
            role: roleCode,
            endpoint: {
              controller: '(N/A)',
              method: '',
              route: '',
              permissionKey: '',
              permissionValue: perm,
              methodName: '',
            },
            message: `${role.name} 预期应有 '${perm}' 权限但当前缺失（无对应端点使用此权限）`,
            suggestion: `在 roles.seed.ts 中为 ${role.name} 添加 '${perm}'`,
          });
        }
      }
    }

    // 检查 denied 中的权限是否在角色中不小心存在
    for (const perm of expectations.denied) {
      if (roleHasPermission(role.permissions, perm)) {
        const affectedEndpoints = protectedEndpoints.filter(
          (e) => e.permissionValue === perm,
        );
        if (affectedEndpoints.length > 0) {
          for (const ep of affectedEndpoints) {
            results.push({
              severity: 'warning',
              role: roleCode,
              endpoint: ep,
              message: `${role.name} 可以访问 ${ep.method} ${ep.route}`,
              suggestion: `${role.name} 不应有 '${perm}' 权限，请从 roles.seed.ts 中移除`,
            });
          }
        } else {
          results.push({
            severity: 'warning',
            role: roleCode,
            endpoint: {
              controller: '(N/A)',
              method: '',
              route: '',
              permissionKey: '',
              permissionValue: perm,
              methodName: '',
            },
            message: `${role.name} 拥有 '${perm}' 权限但不应该有（无对应端点使用此权限）`,
            suggestion: `从 roles.seed.ts 中移除 ${role.name} 的 '${perm}' 权限`,
          });
        }
      }
    }
  }

  return results;
}

// ==================== 7. 无保护端点检测 ====================

function findUnprotectedEndpoints(endpoints: EndpointInfo[]): EndpointInfo[] {
  return endpoints.filter((e) => e.permissionValue === '(none)');
}

// ==================== 8. 报告输出 ====================

function printReport(
  endpoints: EndpointInfo[],
  roles: RolePermissions[],
  results: CheckResult[],
  unprotected: EndpointInfo[],
): void {
  const protectedEndpoints = endpoints.filter(
    (e) => e.permissionValue !== '(none)',
  );

  console.log('');
  console.log('='.repeat(60));
  console.log('  权限矩阵校验报告');
  console.log('='.repeat(60));
  console.log('');
  console.log(`  端点总数: ${endpoints.length}`);
  console.log(`  有权限保护的端点数: ${protectedEndpoints.length}`);
  console.log(`  无权限保护的端点数: ${unprotected.length}`);
  console.log(`  角色数: ${roles.length}`);
  console.log(`  检查的角色: ${Object.keys(ROLE_EXPECTATIONS).join(', ')}`);
  console.log('');

  // 无保护端点
  if (unprotected.length > 0) {
    console.log('-'.repeat(60));
    console.log('  无权限保护的端点（仅需登录）');
    console.log('-'.repeat(60));
    for (const ep of unprotected) {
      console.log(
        `  [INFO] ${ep.method.padEnd(7)} ${ep.route}  (${ep.controller}.${ep.methodName})`,
      );
    }
    console.log('');
  }

  // 错误（权限缺失）
  const errors = results.filter((r) => r.severity === 'error');
  const warnings = results.filter((r) => r.severity === 'warning');

  if (errors.length > 0) {
    console.log('-'.repeat(60));
    console.log('  ❌ 权限缺失');
    console.log('-'.repeat(60));
    for (const r of errors) {
      if (r.endpoint.route) {
        console.log(
          `  [❌ 权限缺失] ${r.message}`,
        );
        console.log(`     需要权限: ${r.endpoint.permissionValue}`);
        console.log(`     → 建议: ${r.suggestion}`);
        console.log('');
      } else {
        console.log(`  [❌ 权限缺失] ${r.message}`);
        console.log(`     → 建议: ${r.suggestion}`);
        console.log('');
      }
    }
  }

  if (warnings.length > 0) {
    console.log('-'.repeat(60));
    console.log('  ⚠️ 过度授权');
    console.log('-'.repeat(60));
    for (const r of warnings) {
      if (r.endpoint.route) {
        console.log(
          `  [⚠️ 过度授权] ${r.message}`,
        );
        console.log(`     权限: ${r.endpoint.permissionValue}`);
        console.log(`     → ${r.suggestion}`);
        console.log('');
      } else {
        console.log(`  [⚠️ 过度授权] ${r.message}`);
        console.log(`     → ${r.suggestion}`);
        console.log('');
      }
    }
  }

  // 汇总每个角色的结果
  console.log('-'.repeat(60));
  console.log('  角色权限汇总');
  console.log('-'.repeat(60));

  for (const roleCode of Object.keys(ROLE_EXPECTATIONS)) {
    const role = roles.find((r) => r.code === roleCode);
    if (!role) continue;

    const roleErrors = errors.filter((r) => r.role === roleCode);
    const roleWarnings = warnings.filter((r) => r.role === roleCode);

    // 计算该角色可正确访问的端点数
    const rolePerms = role.permissions;
    const accessibleCount = protectedEndpoints.filter((ep) =>
      roleHasPermission(rolePerms, ep.permissionValue),
    ).length;

    const statusIcon =
      roleErrors.length > 0 ? '❌' : roleWarnings.length > 0 ? '⚠️' : '✅';

    console.log(
      `  [${statusIcon}] ${role.name}: 可访问 ${accessibleCount}/${protectedEndpoints.length} 个受保护端点` +
        (roleErrors.length > 0 ? `, ${roleErrors.length} 个权限缺失` : '') +
        (roleWarnings.length > 0
          ? `, ${roleWarnings.length} 个过度授权`
          : ''),
    );
  }

  // Administrator 默认拥有所有权限
  const adminRole = roles.find((r) => r.code === 'Administrator');
  if (adminRole) {
    console.log(
      `  [✅] Administrator: 视为拥有所有权限（含通配符 *）`,
    );
  }

  console.log('');

  // 端点清单
  console.log('-'.repeat(60));
  console.log('  端点权限清单');
  console.log('-'.repeat(60));
  console.log(
    `  ${'方法'.padEnd(8)} ${'路由'.padEnd(52)} ${'权限码'}`,
  );
  console.log(`  ${'─'.repeat(8)} ${'─'.repeat(52)} ${'─'.repeat(36)}`);
  for (const ep of endpoints) {
    const permDisplay =
      ep.permissionValue === '(none)' ? '(仅需登录)' : ep.permissionValue;
    console.log(
      `  ${ep.method.padEnd(8)} ${ep.route.padEnd(52)} ${permDisplay}`,
    );
  }

  console.log('');
  console.log('='.repeat(60));

  if (errors.length > 0) {
    console.log(`  结果: ❌ 发现 ${errors.length} 个权限缺失，${warnings.length} 个过度授权`);
  } else if (warnings.length > 0) {
    console.log(`  结果: ⚠️ 无权限缺失，${warnings.length} 个过度授权警告`);
  } else {
    console.log('  结果: ✅ 权限矩阵校验通过');
  }
  console.log('='.repeat(60));
  console.log('');
}

// ==================== 主流程 ====================

function main(): void {
  console.log('正在解析权限常量...');
  const permConsts = parsePermissionsConstants();
  console.log(`  已解析 ${Object.keys(permConsts).length} 个权限常量`);

  console.log('正在扫描 Controller 端点...');
  const endpoints = extractEndpoints(permConsts);
  console.log(`  已提取 ${endpoints.length} 个端点`);

  console.log('正在解析角色种子数据...');
  const roles = extractRolePermissions();
  console.log(`  已提取 ${roles.length} 个角色`);

  console.log('正在执行交叉验证...');
  const results = crossValidate(endpoints, roles);
  const unprotected = findUnprotectedEndpoints(endpoints);

  printReport(endpoints, roles, results, unprotected);

  // 退出码
  const hasErrors = results.some((r) => r.severity === 'error');
  process.exit(hasErrors ? 1 : 0);
}

main();
