/**
 * Delegation Controller for SentryAgent.ai AgentIdP.
 * HTTP handlers for A2A delegation endpoints. No business logic — delegates to DelegationService.
 */

import { Request, Response, NextFunction } from 'express';
import { DelegationService } from '../services/DelegationService.js';
import { AuthorizationError, ValidationError } from '../utils/errors.js';
import {
  delegationsCreatedTotal,
  delegationsVerifiedTotal,
  delegationsRevokedTotal,
} from '../metrics/registry.js';

/**
 * Controller for A2A delegation endpoints.
 * Receives DelegationService via constructor injection.
 */
export class DelegationController {
  /**
   * @param delegationService - The A2A delegation service.
   */
  constructor(private readonly delegationService: DelegationService) {}

  /**
   * Handles POST /oauth2/token/delegate — creates a delegation chain.
   *
   * @param req - Express request with CreateDelegationRequest body.
   * @param res - Express response.
   * @param next - Express next function.
   */
  createDelegation = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      if (!req.user) {
        throw new AuthorizationError();
      }

      const { delegateeAgentId, scopes, ttlSeconds } = req.body as {
        delegateeAgentId?: unknown;
        scopes?: unknown;
        ttlSeconds?: unknown;
      };

      if (typeof delegateeAgentId !== 'string' || !delegateeAgentId) {
        throw new ValidationError('delegateeAgentId is required and must be a string.');
      }
      if (!Array.isArray(scopes) || scopes.length === 0) {
        throw new ValidationError('scopes must be a non-empty array.');
      }
      if (typeof ttlSeconds !== 'number' || !Number.isInteger(ttlSeconds)) {
        throw new ValidationError('ttlSeconds must be an integer.');
      }

      const tenantId = req.user.organization_id ?? 'org_system';
      const delegatorAgentId = req.user.sub;
      const delegatorScopes = req.user.scope ? req.user.scope.split(' ') : [];
      const ipAddress = req.ip ?? '0.0.0.0';
      const userAgent = req.headers['user-agent'] ?? 'unknown';

      const chain = await this.delegationService.createDelegation(
        tenantId,
        delegatorAgentId,
        delegatorScopes,
        { delegateeAgentId, scopes: scopes as string[], ttlSeconds },
        ipAddress,
        userAgent,
      );

      delegationsCreatedTotal.labels(tenantId).inc();

      res.status(201).json({
        delegationToken: chain.delegationToken,
        chainId: chain.id,
        delegatorAgentId: chain.delegatorAgentId,
        delegateeAgentId: chain.delegateeAgentId,
        scopes: chain.scopes,
        expiresAt: chain.expiresAt.toISOString(),
      });
    } catch (err) {
      next(err);
    }
  };

  /**
   * Handles POST /oauth2/token/verify-delegation — verifies a delegation token.
   *
   * @param req - Express request with delegationToken body.
   * @param res - Express response.
   * @param next - Express next function.
   */
  verifyDelegation = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      if (!req.user) {
        throw new AuthorizationError();
      }

      const { delegationToken } = req.body as { delegationToken?: unknown };

      if (typeof delegationToken !== 'string' || !delegationToken) {
        throw new ValidationError('delegationToken is required and must be a string.');
      }

      const agentId = req.user.sub;
      const tenantId = req.user.organization_id ?? 'org_system';
      const ipAddress = req.ip ?? '0.0.0.0';
      const userAgent = req.headers['user-agent'] ?? 'unknown';

      const result = await this.delegationService.verifyDelegation(
        delegationToken,
        agentId,
        ipAddress,
        userAgent,
      );

      const outcomeLabel = result.valid
        ? 'valid'
        : result.revokedAt !== null
          ? 'revoked'
          : new Date() > result.expiresAt
            ? 'expired'
            : 'invalid';

      delegationsVerifiedTotal.labels(tenantId, outcomeLabel).inc();

      res.status(200).json({
        valid: result.valid,
        chainId: result.chainId,
        delegatorAgentId: result.delegatorAgentId,
        delegateeAgentId: result.delegateeAgentId,
        scopes: result.scopes,
        issuedAt: result.issuedAt.toISOString(),
        expiresAt: result.expiresAt.toISOString(),
        revokedAt: result.revokedAt ? result.revokedAt.toISOString() : null,
      });
    } catch (err) {
      next(err);
    }
  };

  /**
   * Handles DELETE /oauth2/token/delegate/:chainId — revokes a delegation chain.
   *
   * @param req - Express request with chainId path param.
   * @param res - Express response.
   * @param next - Express next function.
   */
  revokeDelegation = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      if (!req.user) {
        throw new AuthorizationError();
      }

      const { chainId } = req.params;
      if (!chainId) {
        throw new ValidationError('chainId path parameter is required.');
      }

      const requestingAgentId = req.user.sub;
      const tenantId = req.user.organization_id ?? 'org_system';
      const ipAddress = req.ip ?? '0.0.0.0';
      const userAgent = req.headers['user-agent'] ?? 'unknown';

      await this.delegationService.revokeDelegation(
        chainId,
        requestingAgentId,
        ipAddress,
        userAgent,
      );

      delegationsRevokedTotal.labels(tenantId).inc();

      res.status(204).send();
    } catch (err) {
      next(err);
    }
  };
}
