import { CanActivate, ExecutionContext, ForbiddenException, Injectable } from '@nestjs/common';
import jwt from 'jsonwebtoken';
import type { Request } from 'express';

/**
 * Realtime delegation — the REST half.
 *
 * The browser's socket token is deliberately narrow: `aud = IIOS_REALTIME_AUDIENCE` (e.g.
 * iios-message), minutes-long, and good for the /message socket ONLY. The gateway enforces the
 * mirror of this rule (it accepts ONLY that audience). Without this guard the narrowing is
 * one-directional: REST doesn't check the actor token's audience, so a leaked socket token would
 * still drive privileged REST — i.e. it would be a full actor token with extra steps.
 *
 * Applied GLOBALLY (APP_GUARD) on purpose: every REST route is a target, not just the ones behind
 * ContextAttestationGuard (inbox, media, interactions, ai, … would otherwise stay open).
 *
 * It is a decode-only REJECT filter, never an authenticator:
 *  - it does not verify signatures (each controller still calls SessionVerifier) — cheap, and it
 *    can't be bypassed by stripping `aud`, because that invalidates the signature downstream;
 *  - no bearer token → pass through (health, metrics, HMAC adapter webhooks are not its business);
 *  - unset IIOS_REALTIME_AUDIENCE → no-op (same switch that turns on the socket half).
 */
@Injectable()
export class RealtimeTokenRestGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    if (context.getType() !== 'http') return true; // the socket enforces its own (mirror) rule

    const socketAudience = process.env.IIOS_REALTIME_AUDIENCE?.trim();
    if (!socketAudience) return true;

    const auth = context.switchToHttp().getRequest<Request>().headers['authorization'];
    const raw = Array.isArray(auth) ? auth[0] : auth;
    if (typeof raw !== 'string' || !/^Bearer\s+/i.test(raw)) return true;

    const decoded = jwt.decode(raw.replace(/^Bearer\s+/i, ''), { json: true });
    const aud = decoded?.aud;
    const isSocketToken = aud === socketAudience || (Array.isArray(aud) && aud.includes(socketAudience));
    if (isSocketToken) {
      throw new ForbiddenException(`socket-scoped token (aud="${socketAudience}") cannot be used for REST`);
    }
    return true;
  }
}
