import { CanActivate, ExecutionContext, ForbiddenException, Inject, Injectable, UnauthorizedException } from '@nestjs/common';
import { Request } from 'express';
import { AdminAuthPayload, AdminCoreService } from '../../core/admin_core/admin_core.service';
import { LoginCoreService, JwtPayload } from '../../core/login_core/login_core.service';
import { Users } from '../../core/db/users/users.entity';

export interface TestLabRequest extends Request {
  admin?: AdminAuthPayload;
  user?: JwtPayload;
}

type UsersLookup = {
  findOne(id: bigint): Promise<Users>;
};

/**
 * 测试实验室既可由旧管理端 token 调用，也可由游戏内管理员的登录 token 调用。
 * 两条路径最终都会回查当前用户 role，避免客户端角色字段或旧 token 被单独信任。
 */
@Injectable()
export class TestLabGuard implements CanActivate {
  constructor(
    private readonly adminCore: AdminCoreService,
    private readonly loginCore: LoginCoreService,
    @Inject('UsersService') private readonly users: UsersLookup,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest<TestLabRequest>();
    const token = this.extractBearerToken(request);

    try {
      const admin = this.adminCore.verifyToken(token);
      await this.assertCurrentAdministrator(admin.adminId);
      request.admin = admin;
      return true;
    } catch (_adminTokenError) {
      // 继续尝试游戏登录 token；两类 token 使用不同签名，不会相互放行。
    }

    try {
      const user = await this.loginCore.verifyToken(token, 'access');
      if (user.role !== 9) throw new ForbiddenException('仅管理员可使用测试实验室');
      await this.assertCurrentAdministrator(user.sub);
      request.user = user;
      request.admin = {
        adminId: user.sub,
        username: user.username,
        role: 9,
        iat: user.iat || Math.floor(Date.now() / 1000),
        exp: user.exp || Math.floor(Date.now() / 1000),
      };
      return true;
    } catch (error) {
      if (error instanceof ForbiddenException) throw error;
      throw new UnauthorizedException('需要有效的管理员登录凭据');
    }
  }

  private extractBearerToken(request: Request): string {
    const authorization = request.headers.authorization;
    if (!authorization || Array.isArray(authorization)) throw new UnauthorizedException('缺少Authorization头');
    const [scheme, token] = authorization.split(' ');
    if (scheme !== 'Bearer' || !token) throw new UnauthorizedException('Authorization格式错误');
    return token;
  }

  private async assertCurrentAdministrator(userId: string): Promise<void> {
    if (!/^\d+$/.test(userId)) throw new UnauthorizedException('管理员身份无效');
    const user = await this.users.findOne(BigInt(userId));
    if (user.role !== 9) throw new ForbiddenException('仅管理员可使用测试实验室');
  }
}
