/**
 * Authentication middleware for SentryAgent.ai AgentIdP.
 * Extracts and verifies Bearer tokens from the Authorization header.
 * Checks Redis for token revocation before attaching the payload to req.user.
 */

import { Request, Response, NextFunction } from 'express';
import { JsonWebTokenError, TokenExpiredError } from 'jsonwebtoken';
import { verifyToken } from '../utils/jwt.js';
import { getRedisClient } from '../cache/redis.js';
import { AuthenticationError } from '../utils/errors.js';
import { ITokenPayload } from '../types/index.js';
import { tenantApiCallsTotal } from '../metrics/registry.js';

/**
 * Express middleware that validates a Bearer JWT token on every protected request.
 *
 * Behaviour:
 * 1. Extracts the Bearer token from the Authorization header.
 * 2. Verifies the RS256 signature and expiry using the public key.
 * 3. Checks Redis whether the JTI has been explicitly revoked.
 * 4. Attaches the decoded payload to `req.user`.
 * 5. Throws `AuthenticationError` on any failure.
 *
 * @param req - Express request.
 * @param _res - Express response (unused).
 * @param next - Express next function.
 */
export async function authMiddleware(
  req: Request,
  _res: Response,
  next: NextFunction,
): Promise<void> {
  try {
    const authHeader = req.headers['authorization'];

    if (!authHeader || !authHeader.startsWith('Bearer ')) {
      throw new AuthenticationError('A valid Bearer token is required to access this resource.');
    }

    const token = authHeader.slice(7).trim();
    if (!token) {
      throw new AuthenticationError('A valid Bearer token is required to access this resource.');
    }

    const publicKey = process.env['JWT_PUBLIC_KEY'];
    if (!publicKey) {
      throw new Error('JWT_PUBLIC_KEY environment variable is required');
    }

    let payload: ITokenPayload;
    try {
      payload = verifyToken(token, publicKey);
    } catch (err) {
      if (err instanceof TokenExpiredError) {
        throw new AuthenticationError('Token has expired.');
      }
      if (err instanceof JsonWebTokenError) {
        throw new AuthenticationError('Token signature is invalid.');
      }
      throw new AuthenticationError();
    }

    // Check Redis revocation list
    const redis = await getRedisClient();
    const revocationKey = `revoked:${payload.jti}`;
    const isRevoked = await redis.get(revocationKey);

    if (isRevoked !== null) {
      throw new AuthenticationError('Token has been revoked.');
    }

    // Multi-tenancy: ensure organization_id is always populated on the payload.
    // Tokens issued before multi-tenancy (without organization_id claim) are
    // back-filled with the DEFAULT_ORG_ID for backward compatibility.
    const multiTenancyEnabled = process.env['MULTI_TENANCY_ENABLED'] !== 'false';
    if (multiTenancyEnabled && !payload.organization_id) {
      payload.organization_id = process.env['DEFAULT_ORG_ID'] ?? 'org_system';
    }

    req.user = payload;

    // Instrument: count authenticated API calls per tenant (WS4 — Agent Marketplace)
    const tenantId = payload.organization_id ?? 'unknown';
    tenantApiCallsTotal.inc({ tenant_id: tenantId });

    next();
  } catch (err) {
    next(err);
  }
}
