import {
  CanActivate,
  ExecutionContext,
  Injectable,
  UnauthorizedException,
} from '@nestjs/common';
import { jwtVerify } from 'jose';
import type { JWTPayload } from 'jose';
import { UsersService } from '../../users/src';
import { OidcDiscoveryService } from './oidc-discovery.service';

export interface AuthenticatedUser {
  id: string;
  externalSubjectId: string;
  displayName: string;
  email: string;
}

@Injectable()
export class OidcAuthGuard implements CanActivate {
  constructor(
    private readonly discovery: OidcDiscoveryService,
    private readonly users: UsersService,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest<{
      headers: Record<string, string | undefined>;
      user?: AuthenticatedUser;
    }>();
    const header = request.headers?.authorization;
    const token = header?.startsWith('Bearer ') ? header.slice(7) : undefined;
    if (!token) throw new UnauthorizedException('Missing bearer token');

    let payload: JWTPayload;
    try {
      const result = await jwtVerify(
        token,
        this.discovery.getVerificationKeySet(),
        {
          issuer: this.discovery.getIssuer(),
          audience: this.discovery.getAudience(),
        },
      );
      payload = result.payload;
    } catch {
      throw new UnauthorizedException('Invalid or expired token');
    }

    const sub = payload.sub;
    if (!sub) throw new UnauthorizedException('Token has no subject claim');

    const user = await this.users.findOrCreateByExternalSubjectId(sub, {
      email: (payload.email as string) ?? '',
      displayName: (payload.name as string) ?? (payload.email as string) ?? sub,
    });

    request.user = {
      id: user.id,
      externalSubjectId: user.externalSubjectId,
      displayName: user.displayName,
      email: user.email,
    };
    return true;
  }
}
