/**
 * WebSocket 认证守卫
 * 用于验证 Socket.IO 连接的用户身份
 */

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import type { Socket } from 'socket.io';
import { WsException } from '@nestjs/websockets';
import { RedisDatabase, RedisService } from '../../plugins/redis/redis.service';
import { LoggerService } from '../../plugins/logger/logger.service';
import { MeetingService } from '../meeting/meeting.service';
import type { MeetingWsUser } from './types';

@Injectable()
export class MeetingAuthGuard implements CanActivate {
  public constructor(
    private readonly redisService: RedisService,
    private readonly logger: LoggerService,
    private readonly meetingService: MeetingService
  ) {}

  public async canActivate(context: ExecutionContext): Promise<boolean> {
    // 获取 Socket 实例
    const socket = context.switchToWs().getClient<Socket>();

    // 检查是否已经通过连接认证
    if (socket.data.user) {
      return true;
    }

    try {
      const user = await this.validateToken(socket);
      socket.data.user = user;
      return true;
    } catch (e) {
      if (e instanceof WsException) {
        this.logger.error(e.message, 'WsAuthGuard');
        throw e;
      }
      this.logger.error(e, 'WsAuthGuard');
      throw new WsException('WebSocket 鉴权失败');
    }
  }

  /**
   * 验证 Token 并返回用户信息
   * 可供 handleConnection 直接调用
   */
  public async validateToken(socket: Socket): Promise<MeetingWsUser> {
    // 尝试从握手信息中获取认证头（兼容大小写）
    const handshake = socket.handshake;
    const authHeader = String(
      handshake.auth?.authorization || handshake.auth?.Authorization || handshake.headers?.authorization || handshake.headers?.Authorization || ''
    );
    const customMac = String(
      handshake.auth?.['custom-mac'] || handshake.auth?.['Custom-Mac'] || handshake.headers?.['custom-mac'] || handshake.headers?.['Custom-Mac'] || ''
    );
    const customPlatform = String(
      handshake.auth?.['custom-platform'] ||
        handshake.auth?.['Custom-Platform'] ||
        handshake.headers?.['custom-platform'] ||
        handshake.headers?.['Custom-Platform'] ||
        ''
    );
    const customTimestamp = String(
      handshake.auth?.['custom-timestamp'] ||
        handshake.auth?.['Custom-Timestamp'] ||
        handshake.headers?.['custom-timestamp'] ||
        handshake.headers?.['Custom-Timestamp'] ||
        ''
    );

    // 验证必要的认证信息
    if (!authHeader || !authHeader.toLowerCase().startsWith('bearer')) {
      throw new WsException('缺少或无效的 Authorization');
    }
    if (!customMac) {
      throw new WsException('缺少 Custom-Mac');
    }
    if (!customPlatform) {
      throw new WsException('缺少 Custom-Platform');
    }
    if (!customTimestamp) {
      throw new WsException('缺少 Custom-Timestamp');
    }

    // 解析 Token
    const token = authHeader.slice(7).trim();
    const parts = token.split('.');

    if (parts.length < 2) {
      throw new WsException('无效的 Token 格式');
    }

    const payload: any = (() => {
      try {
        const json = Buffer.from(parts[1], 'base64url').toString('utf8');
        return JSON.parse(json);
      } catch {
        throw new WsException('无法解析 Token');
      }
    })();
    const NAME_ID_CLAIM = 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier';
    const ROLE_CLAIM = 'http://schemas.microsoft.com/ws/2008/06/identity/claims/role';
    const userId = String(payload?.[NAME_ID_CLAIM] || '');
    const role = Number(payload?.[ROLE_CLAIM] || '-1');

    if (!userId || isNaN(role) || role < 0) {
      throw new WsException('Token 非法');
    }

    // 验证 Token 过期时间
    const nowSec = Math.floor(Date.now() / 1000);
    const exp = Number(payload?.exp || 0);
    if (!Number.isFinite(exp) || nowSec >= exp) {
      throw new WsException('Token 已过期');
    }

    const platform = customPlatform.toUpperCase();

    // 验证 Redis 中的登录态
    const redisKey = `Auth:${userId}:${platform}`;
    const redis = this.redisService.getClient(RedisDatabase.GLOBAL);
    const v = await redis.get(redisKey);

    if (!v) {
      throw new WsException('未找到登录态');
    }

    const redisObj: any = (() => {
      try {
        return JSON.parse(v);
      } catch {
        throw new WsException('登录态数据异常');
      }
    })();

    const redisToken = redisObj?.Token || redisObj?.token || '';
    // 因为会有同一个账号同平台登录多个（监控端和教师端使用同一个账号也都是web所以不做过多验证）
    if (!redisToken) {
      throw new WsException('登录态已失效');
    }

    // 查询数据库获取短 UID 和用户名
    const shortUid = await this.meetingService.getOrAssignShortUid(Number(userId));
    // 从 Redis 登录态中获取用户名
    const userName = redisObj?.userName || redisObj?.UserName || `用户-${userId}`;
    console.log(`[WsAuthGuard] 用户 ${userId} 获得短 UID: ${shortUid}, userName: ${userName}`);

    return {
      userId,
      role,
      shortUid,
      platform,
      userName,
    };
  }
}
