import { CanActivate, ExecutionContext, ForbiddenException, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import type { Permission, AccessTokenPayload } from '@pac/types';
import { ROLE_PERMISSIONS } from '@pac/types';
import { PERMISSIONS_KEY } from '../decorators/permissions.decorator';
import { IS_PUBLIC_KEY } from '../decorators/public.decorator';

@Injectable()
export class PermissionsGuard implements CanActivate {
  constructor(private readonly reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);
    if (isPublic) return true;

    const required = this.reflector.getAllAndOverride<Permission[] | undefined>(PERMISSIONS_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);
    if (!required || required.length === 0) return true;

    const req = context.switchToHttp().getRequest<{ user?: AccessTokenPayload }>();
    const user = req.user;
    if (!user) throw new ForbiddenException('No authenticated user');

    // ⭐ 按 **role 现算**,不认 JWT 里那份 permissions 快照。
    //   JWT 的 permissions 是签发那一刻 resolvePermissions(role) 的结果(auth.service 是唯一产地,
    //   从没有过"自定义权限集"这条路),access token 2h 不变 —— 于是**新增一个权限的那次发版**,
    //   已登录的人会在 token 到期前一直按老清单判定:leader 拿不到 plan:dispatch,
    //   前端按钮藏着、MCP 工具集静默少几个,**不报错不告警**,只能靠"让所有人重登"兜。
    //   改成现算后 ROLE_PERMISSIONS 是唯一真理源(claim-guard.ts 早已这么写),
    //   JWT 那份降级为缓存,只在 role 不认识时兜底。
    //   ⚠️ 不会放大权限:role 本身仍来自签名过的 JWT,这里只是把 role→permissions 的映射
    //   从"签发时固化"改成"检查时现算",改角色仍须重新签票。
    const granted = new Set<string>(ROLE_PERMISSIONS[user.role] ?? user.permissions ?? []);
    const missing = required.filter((p) => !granted.has(p));
    if (missing.length > 0) {
      throw new ForbiddenException(`Missing permissions: ${missing.join(', ')}`);
    }
    return true;
  }
}
