/**
 * OIDCController — request handlers for OIDC discovery, JWKS, and agent-info endpoints.
 *
 * Handlers:
 *   GET /.well-known/openid-configuration → getDiscoveryDocument
 *   GET /.well-known/jwks.json            → getJWKS
 *   GET /agent-info                        → getAgentInfo (protected)
 */

import { Request, Response, NextFunction } from 'express';

import { OIDCKeyService } from '../services/OIDCKeyService.js';
import { AgentRepository } from '../repositories/AgentRepository.js';
import { AgentNotFoundError } from '../utils/errors.js';
import {
  IOIDCDiscoveryDocument,
  IJWKSResponse,
  IAgentInfoResponse,
} from '../types/oidc.js';

/**
 * Controller for all OIDC-related endpoints.
 * Delegates key management to OIDCKeyService and agent lookups to AgentRepository.
 */
export class OIDCController {
  /**
   * @param oidcKeyService - Service managing OIDC signing key pairs and JWKS.
   * @param agentRepository - Repository for agent identity lookups.
   */
  constructor(
    private readonly oidcKeyService: OIDCKeyService,
    private readonly agentRepository: AgentRepository,
  ) {}

  // ─────────────────────────────────────────────────────────────────────────────
  // Public handlers
  // ─────────────────────────────────────────────────────────────────────────────

  /**
   * Returns the OpenID Connect Discovery 1.0 document.
   * No authentication required — this is a public metadata endpoint.
   *
   * @param _req - Express request (unused).
   * @param res - Express response.
   * @param _next - Express next function (unused — no async errors possible).
   */
  async getDiscoveryDocument(
    _req: Request,
    res: Response,
    _next: NextFunction,
  ): Promise<void> {
    const issuer = process.env['OIDC_ISSUER'] ?? 'https://idp.sentryagent.ai';

    const document: IOIDCDiscoveryDocument = {
      issuer,
      authorization_endpoint: `${issuer}/oauth2/authorize`,
      token_endpoint: `${issuer}/oauth2/token`,
      jwks_uri: `${issuer}/.well-known/jwks.json`,
      response_types_supported: ['token', 'id_token'],
      subject_types_supported: ['public'],
      id_token_signing_alg_values_supported: ['RS256', 'ES256'],
      scopes_supported: [
        'openid',
        'agents:read',
        'agents:write',
        'tokens:read',
        'audit:read',
        'admin:orgs',
      ],
      claims_supported: [
        'sub',
        'iss',
        'aud',
        'iat',
        'exp',
        'agent_type',
        'deployment_env',
        'organization_id',
        'did',
      ],
      grant_types_supported: ['client_credentials'],
    };

    res.status(200).json(document);
  }

  /**
   * Returns the JWKS (JSON Web Key Set) for ID token verification.
   * Includes all non-expired keys to support rotation grace periods.
   * Sets a Cache-Control header to allow public caching for 1 hour.
   * No authentication required — this is a public key material endpoint.
   *
   * @param _req - Express request (unused).
   * @param res - Express response.
   * @param next - Express next function — forwards errors to global error handler.
   */
  async getJWKS(_req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const jwks: IJWKSResponse = await this.oidcKeyService.getPublicJWKS();
      res.set('Cache-Control', 'public, max-age=3600');
      res.status(200).json(jwks);
    } catch (err) {
      next(err);
    }
  }

  /**
   * Returns agent identity claims for the authenticated caller.
   * Equivalent to OIDC UserInfo endpoint — protected by authMiddleware.
   * The authenticated agent's UUID is taken from `req.user.sub`.
   *
   * @param req - Express request with `req.user` populated by authMiddleware.
   * @param res - Express response.
   * @param next - Express next function — forwards errors to global error handler.
   * @throws AgentNotFoundError if the agent referenced in the token no longer exists.
   */
  async getAgentInfo(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      if (!req.user) {
        // authMiddleware guarantees this; guard for type safety
        throw new Error('req.user is not populated. Ensure authMiddleware runs first.');
      }

      const agentId = req.user.sub;
      const agent = await this.agentRepository.findById(agentId);
      if (!agent) {
        throw new AgentNotFoundError(agentId);
      }

      const response: IAgentInfoResponse = {
        sub: agent.agentId,
        agent_type: agent.agentType,
        deployment_env: agent.deploymentEnv,
        organization_id: agent.organizationId,
        scope: req.user.scope,
      };

      if (agent.did !== undefined) {
        response.did = agent.did;
      }

      res.status(200).json(response);
    } catch (err) {
      next(err);
    }
  }
}
