/**
 * student-bff AuthorizationGuard - B4 自我越权防御.
 *
 * 仲裁依据:
 *   - coord-final-decisions §2 B4 (全部 BFF 强制自我越权防御)
 *   - president-final-rulings §2.9 (越权防御 P3 实现方式: 方案 D)
 *   - president-final-rulings §2.7 (3 类越权防御错误码)
 *
 * 防御策略:
 *   - 场景 A (资源无归属): 学生请求的 studentId 与 JWT userId 不一致
 *     → ForbiddenResourceError (403, BFF_STUDENT_FORBIDDEN_RESOURCE)
 *   - 场景 B (身份不一致): JWT userId 与请求 body userId 不一致
 *     → IdentityMismatchError (403, BFF_STUDENT_IDENTITY_MISMATCH)
 *
 * DEV_MODE=true 时放行 (president §2.9 方案 D):
 *   - 本地开发无 JWT 时, DEV_MODE=true 跳过越权校验
 *   - 生产环境 DEV_MODE=false 强制校验
 */
import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common";
import type { Request } from "express";
import { env } from "../../config/env.js";
import {
  UnauthorizedError,
  ForbiddenResourceError,
  IdentityMismatchError,
} from "../../shared/errors/application-error.js";
import { logger } from "../../shared/observability/logger.js";

/**
 * 学生越权防御 Guard.
 *
 * 使用方式 (NestJS):
 *   @UseGuards(AuthorizationGuard)
 *   @Query(() => StudentDashboard)
 *   async studentDashboard(@Context('userId') userId: string, ...) {}
 *
 * GraphQL Yoga 集成: 在 context 构建时调用 extractUserIdFromRequest,
 *   并在 Resolver 内显式调用 assertOwnData(userId, requestedStudentId).
 */
@Injectable()
export class AuthorizationGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    if (env.DEV_MODE) {
      // 方案 D: DEV_MODE=true 跳过越权校验 (本地开发无 JWT)
      return true;
    }

    const request = context.switchToHttp().getRequest<Request>();
    const userId = extractUserIdFromRequest(request);

    if (!userId) {
      throw new UnauthorizedError();
    }

    return true;
  }
}

/**
 * 从 Express Request 提取 userId (由 api-gateway 注入 x-user-id 头).
 *
 * BFF 不验签 JWT, 仅读 header (B3 裁决).
 */
export function extractUserIdFromRequest(request: Request): string | null {
  const header = request.headers["x-user-id"];
  if (typeof header === "string" && header.length > 0) {
    return header;
  }
  return null;
}

/**
 * 从 Express Request 提取 traceId (由 api-gateway 注入 x-request-id 头).
 */
export function extractTraceIdFromRequest(request: Request): string {
  const header = request.headers["x-request-id"];
  return typeof header === "string" && header.length > 0 ? header : "unknown";
}

/**
 * 提取 x-user-roles 头 (列表, 逗号分隔).
 */
export function extractUserRolesFromRequest(request: Request): string[] {
  const header = request.headers["x-user-roles"];
  if (typeof header === "string" && header.length > 0) {
    return header.split(",").map((r) => r.trim()).filter(Boolean);
  }
  return [];
}

/**
 * 强制 userId = requestedStudentId (B4 自我越权防御场景 A).
 *
 * 学生只能查/操作自己的数据. 在 Resolver 内调用:
 *   assertOwnData(userId, args.studentId);
 *
 * @param userId JWT 中的 userId (从 x-user-id 头)
 * @param requestedStudentId 请求参数中的 studentId (args / input)
 * @throws ForbiddenResourceError 当 requestedStudentId ≠ userId
 */
export function assertOwnData(
  userId: string,
  requestedStudentId?: string | null,
): void {
  if (env.DEV_MODE) {
    return;
  }
  if (requestedStudentId && requestedStudentId !== userId) {
    logger.warn(
      { userId, requestedStudentId },
      "Authorization blocked: student data scope violation",
    );
    throw new ForbiddenResourceError(
      "Students can only access their own data",
      { requested: requestedStudentId, actual: userId },
    );
  }
}

/**
 * 校验 JWT userId 与 body userId 一致 (B4 场景 B).
 *
 * 用于 Mutation 输入校验: 如 submitHomework input 内 studentId 必须 = JWT userId.
 *
 * @param userId JWT 中的 userId
 * @param bodyUserId 请求 body 中的 userId 字段
 * @throws IdentityMismatchError 当 bodyUserId ≠ userId
 */
export function assertIdentityMatch(
  userId: string,
  bodyUserId?: string | null,
): void {
  if (env.DEV_MODE) {
    return;
  }
  if (bodyUserId && bodyUserId !== userId) {
    logger.warn(
      { userId, bodyUserId },
      "Authorization blocked: identity mismatch",
    );
    throw new IdentityMismatchError(
      "JWT userId does not match request body userId",
      { jwt: userId, body: bodyUserId },
    );
  }
}
