/**
 * Compliance routes for SentryAgent.ai AgentIdP.
 *
 * SOC 2 Type II routes (always active):
 *   GET /api/v1/audit/verify        — Audit chain integrity (requires audit:read)
 *   GET /api/v1/compliance/controls — SOC 2 control status (public, no auth)
 *
 * AGNTCY compliance routes (gated by COMPLIANCE_ENABLED env var):
 *   GET /api/v1/compliance/report      — AGNTCY compliance report (requires auth)
 *   GET /api/v1/compliance/agent-cards — AGNTCY agent card export (requires auth)
 *
 * When COMPLIANCE_ENABLED=false, the AGNTCY routes return 404.
 * The SOC 2 routes are never gated.
 */

import { Router, Request, Response, NextFunction, RequestHandler } from 'express';
import { ComplianceController } from '../controllers/ComplianceController.js';
import { authMiddleware } from '../middleware/auth.js';
import { asyncHandler } from '../utils/asyncHandler.js';
import { InsufficientScopeError } from '../utils/errors.js';
import { ITokenPayload } from '../types/index.js';

// ============================================================================
// Scope guard
// ============================================================================

/**
 * Returns an Express middleware that verifies the caller's JWT contains the
 * specified OAuth 2.0 scope. Must run after `authMiddleware`.
 *
 * @param requiredScope - The scope string that must be present in `req.user.scope`.
 * @returns An async Express RequestHandler that throws InsufficientScopeError if the scope is absent.
 */
function requireScope(requiredScope: string): RequestHandler {
  return async (req: Request, _res: Response, next: NextFunction): Promise<void> => {
    try {
      const user = req.user as ITokenPayload | undefined;
      if (!user) {
        throw new InsufficientScopeError(requiredScope);
      }
      const scopes = user.scope.split(' ');
      if (!scopes.includes(requiredScope)) {
        throw new InsufficientScopeError(requiredScope);
      }
      next();
    } catch (err) {
      next(err);
    }
  };
}

// ============================================================================
// In-memory rate limiter (30 req/min per client_id — for audit/verify endpoint)
// ============================================================================

/** Per-key request count within the current minute window. */
const windowCounts = new Map<string, { count: number; windowKey: number }>();

/** Rate limit maximum for audit/verify (30/min — computationally intensive). */
const AUDIT_RATE_LIMIT_MAX = 30;

/** Window duration in milliseconds (1 minute). */
const AUDIT_WINDOW_MS = 60000;

/**
 * In-memory rate limiter middleware for the audit/verify endpoint.
 * Enforces 30 requests per minute per client_id.
 * Falls back to IP address for unauthenticated requests.
 *
 * @param req - Express request.
 * @param res - Express response.
 * @param next - Express next function.
 */
async function auditRateLimiter(
  req: Request,
  res: Response,
  next: NextFunction,
): Promise<void> {
  try {
    const clientKey = req.user?.client_id ?? req.ip ?? 'unknown';
    const windowKey = Math.floor(Date.now() / AUDIT_WINDOW_MS);
    const resetAt = Math.floor(((windowKey + 1) * AUDIT_WINDOW_MS) / 1000);

    const existing = windowCounts.get(clientKey);
    let count = 1;
    if (existing && existing.windowKey === windowKey) {
      existing.count += 1;
      count = existing.count;
    } else {
      windowCounts.set(clientKey, { count: 1, windowKey });
    }

    const remaining = Math.max(0, AUDIT_RATE_LIMIT_MAX - count);
    res.setHeader('X-RateLimit-Limit', AUDIT_RATE_LIMIT_MAX);
    res.setHeader('X-RateLimit-Remaining', remaining);
    res.setHeader('X-RateLimit-Reset', resetAt);

    if (count > AUDIT_RATE_LIMIT_MAX) {
      res.status(429).json({
        code: 'RATE_LIMIT_EXCEEDED',
        message: 'Too many requests. Please retry after the rate limit window resets.',
      });
      return;
    }

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

// ============================================================================
// Router factory
// ============================================================================

/**
 * Creates and returns the Express router for compliance endpoints.
 *
 * SOC 2 routes (always mounted, never gated):
 *   GET /audit/verify        — Verify audit chain integrity (Bearer + audit:read scope)
 *   GET /compliance/controls — Get SOC 2 control status (public, no auth required)
 *
 * AGNTCY routes (mounted only when COMPLIANCE_ENABLED != 'false'):
 *   GET /compliance/report      — AGNTCY compliance report (Bearer auth required)
 *   GET /compliance/agent-cards — AGNTCY agent card export (Bearer auth required)
 *
 * @param complianceController - The compliance controller instance.
 * @returns Configured Express router.
 */
export function createComplianceRouter(complianceController: ComplianceController): Router {
  const router = Router();

  // ── SOC 2 routes — always active ──────────────────────────────────────────

  // GET /audit/verify — requires authentication + audit:read scope + rate limit
  router.get(
    '/audit/verify',
    asyncHandler(authMiddleware),
    requireScope('audit:read'),
    asyncHandler(auditRateLimiter),
    asyncHandler(complianceController.verifyAuditChain.bind(complianceController)),
  );

  // GET /compliance/controls — public, no auth required
  router.get(
    '/compliance/controls',
    asyncHandler(complianceController.getComplianceControls.bind(complianceController)),
  );

  // ── AGNTCY compliance routes — gated by COMPLIANCE_ENABLED flag ───────────

  if (process.env['COMPLIANCE_ENABLED'] !== 'false') {
    // GET /compliance/report — requires Bearer auth; returns AGNTCY compliance report
    router.get(
      '/compliance/report',
      asyncHandler(authMiddleware),
      asyncHandler(complianceController.getComplianceReport.bind(complianceController)),
    );

    // GET /compliance/agent-cards — requires Bearer auth; returns AGNTCY agent card array
    router.get(
      '/compliance/agent-cards',
      asyncHandler(authMiddleware),
      asyncHandler(complianceController.exportAgentCards.bind(complianceController)),
    );
  }

  return router;
}
