import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";
import { IS_PUBLIC_KEY } from "../decorators/public.decorator";
import { PrismaService } from "../prisma/prisma.service";

@Injectable()
export class JwtAuthGuard implements CanActivate {
  constructor(private readonly jwt: JwtService, private readonly prisma: PrismaService) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    // Check @Public() directly from metadata without Reflector
    const isPublic =
      Reflect.getMetadata(IS_PUBLIC_KEY, context.getHandler()) ||
      Reflect.getMetadata(IS_PUBLIC_KEY, context.getClass());
    if (isPublic) return true;

    const req = context.switchToHttp().getRequest();
    const authorization = req.headers.authorization as string | undefined;
    if (!authorization) {
      throw new UnauthorizedException("authorization_required");
    }

    const parts = authorization.trim().split(/\s+/);
    const [scheme, token] = parts;
    if (parts.length !== 2 || !scheme || !token || token.length > 4096 || scheme.toLowerCase() !== "bearer") {
      throw new UnauthorizedException("invalid_authorization_format");
    }

    const secret = process.env.JWT_ACCESS_SECRET;
    if (!secret) {
      throw new UnauthorizedException("server_misconfigured");
    }

    let payload: { sub: string; tokenVersion?: number; [key: string]: unknown };
    try {
      payload = await this.jwt.verifyAsync(token, {
        secret,
        issuer: "mawjod-api",
        audience: "mawjod-client",
        algorithms: ["HS256"],
      });
    } catch (error) {
      throw new UnauthorizedException(error instanceof Error && error.name === "TokenExpiredError" ? "access_token_expired" : "invalid_access_token");
    }
    if (typeof payload.sub !== "string" || !payload.sub || payload.tokenType !== "access") throw new UnauthorizedException("invalid_access_token");
    // Database failures must remain server errors, not trigger session deletion.
    const user = await this.prisma.user.findUnique({ where: { id: payload.sub } });
    if (!user?.isActive || ["SUSPENDED", "BANNED", "DELETED"].includes(user.status) || (payload.tokenVersion ?? 0) !== user.tokenVersion) throw new UnauthorizedException("session_revoked");
    req.user = { ...payload, role: user.role, permissions: user.permissions };
    return true;
  }
}
