import { type CanActivate, type ExecutionContext, ForbiddenException, Injectable } from '@nestjs/common';
import type { Request } from 'express';
import { SessionVerifier } from './session.verifier';
import { ContextAttestationVerifier } from './context-attestation';

/**
 * The July 12 three-proof gate on the request path. Proof #1 (a valid actor token) is done by
 * the controllers; proof #2 (workload/mTLS) is the mesh's job in prod. This guard adds proof #3:
 * a signed CONTEXT ATTESTATION from an authorized parent (AppShell etc.).
 *
 * Behaviour (safe rollout):
 *   • attestation header present → verify it; the attestation's app_id must match the actor
 *     token's app_id. Any failure → 403.
 *   • attestation header absent  → allowed ONLY when not required (dev / a zero-trust standalone
 *     caller that IIOS checks fully itself). With `IIOS_REQUIRE_ATTESTATION=1`, absence is a 403.
 *
 * The requirement is read per-request so it can be toggled without restarts and stays OFF by
 * default — existing callers that don't send an attestation keep working until the rollout flips.
 */
@Injectable()
export class ContextAttestationGuard implements CanActivate {
  constructor(
    private readonly session: SessionVerifier,
    private readonly attest: ContextAttestationVerifier,
  ) {}

  async canActivate(ctx: ExecutionContext): Promise<boolean> {
    const required = process.env.IIOS_REQUIRE_ATTESTATION === '1';
    const req = ctx.switchToHttp().getRequest<Request>();
    const raw = req.headers['x-context-attestation'];
    const attestation = Array.isArray(raw) ? raw[0] : raw;

    if (!attestation) {
      if (required) throw new ForbiddenException('context attestation required');
      return true; // dev / zero-trust: the actor token is still enforced downstream
    }

    // Bind the attestation to the token's app_id, so a stolen attestation for another app fails.
    const auth = req.headers['authorization'] ?? '';
    const token = String(auth).replace(/^Bearer\s+/i, '');
    let appId: string;
    try {
      appId = this.session.verify(token).appId ?? '';
    } catch {
      throw new ForbiddenException('invalid actor token');
    }

    const result = await this.attest.verify(attestation, appId);
    if (!result.ok) throw new ForbiddenException(`context attestation rejected: ${result.reason}`);
    return true;
  }
}
