import {
  Injectable,
  CanActivate,
  ExecutionContext,
  createParamDecorator,
  SetMetadata,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { JwtService } from '@nestjs/jwt';
import { UnauthorizedException } from '@/shared/exceptions/domain.exception';

export interface JwtPayload {
  userId: string;
  accountSequence: string; // 格式: D + YYMMDD + 5位序号
  deviceId: string;
  type: 'access' | 'refresh';
}

export interface CurrentUserData {
  userId: string;
  accountSequence: string; // 格式: D + YYMMDD + 5位序号
  deviceId: string;
}

export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);

export const CurrentUser = createParamDecorator(
  (
    data: keyof CurrentUserData | undefined,
    ctx: ExecutionContext,
  ): CurrentUserData | string | number => {
    const request = ctx.switchToHttp().getRequest();
    const user = request.user as CurrentUserData;
    return data ? user?.[data] : user;
  },
);

@Injectable()
export class JwtAuthGuard implements CanActivate {
  constructor(
    private readonly jwtService: JwtService,
    private readonly reflector: Reflector,
  ) {}

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

    const request = context.switchToHttp().getRequest();
    const token = this.extractTokenFromHeader(request);

    if (!token) throw new UnauthorizedException('缺少认证令牌');

    try {
      const payload = await this.jwtService.verifyAsync<JwtPayload>(token);
      if (payload.type !== 'access')
        throw new UnauthorizedException('无效的令牌类型');
      request.user = {
        userId: payload.userId,
        accountSequence: payload.accountSequence,
        deviceId: payload.deviceId,
      };
    } catch {
      throw new UnauthorizedException('令牌无效或已过期');
    }

    return true;
  }

  private extractTokenFromHeader(request: any): string | undefined {
    const [type, token] = request.headers.authorization?.split(' ') ?? [];
    return type === 'Bearer' ? token : undefined;
  }
}
