/**
 * Credential Controller for SentryAgent.ai AgentIdP.
 * HTTP handlers for all 4 credential management endpoints.
 */

import { Request, Response, NextFunction } from 'express';
import { CredentialService } from '../services/CredentialService.js';
import {
  generateCredentialSchema,
  listCredentialsQuerySchema,
} from '../utils/validators.js';
import { ValidationError, AuthorizationError, AuthenticationError } from '../utils/errors.js';
import {
  IGenerateCredentialRequest,
  ICredentialListFilters,
} from '../types/index.js';

/**
 * Controller for the Credential Management endpoints.
 */
export class CredentialController {
  /**
   * @param credentialService - The credential management service.
   */
  constructor(private readonly credentialService: CredentialService) {}

  /**
   * Handles POST /agents/:agentId/credentials — generates new credentials.
   * Returns 201 with CredentialWithSecret (secret shown once only).
   *
   * @param req - Express request.
   * @param res - Express response.
   * @param next - Express next function.
   */
  generateCredential = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      if (!req.user) {
        throw new AuthenticationError();
      }

      const { agentId } = req.params;

      // An agent may only manage its own credentials (Phase 1 — no admin scope)
      if (req.user.sub !== agentId) {
        throw new AuthorizationError('You do not have permission to manage credentials for this agent.');
      }

      // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
      const { error, value } = generateCredentialSchema.validate(req.body ?? {}, {
        abortEarly: false,
      });
      if (error) {
        throw new ValidationError('Request validation failed.', {
          details: error.details.map((d) => ({ field: d.path.join('.'), reason: d.message })),
        });
      }

      const data = value as IGenerateCredentialRequest;
      const ipAddress = req.ip ?? '0.0.0.0';
      const userAgent = req.headers['user-agent'] ?? 'unknown';

      const result = await this.credentialService.generateCredential(
        agentId,
        data,
        ipAddress,
        userAgent,
      );
      res.status(201).json(result);
    } catch (err) {
      next(err);
    }
  };

  /**
   * Handles GET /agents/:agentId/credentials — lists credentials for an agent.
   * clientSecret is never returned in list responses.
   *
   * @param req - Express request.
   * @param res - Express response.
   * @param next - Express next function.
   */
  listCredentials = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      if (!req.user) {
        throw new AuthenticationError();
      }

      const { agentId } = req.params;

      if (req.user.sub !== agentId) {
        throw new AuthorizationError('You do not have permission to manage credentials for this agent.');
      }

      // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
      const { error, value } = listCredentialsQuerySchema.validate(req.query, {
        abortEarly: false,
      });
      if (error) {
        throw new ValidationError('Invalid query parameter value.', {
          details: error.details.map((d) => ({ field: d.path.join('.'), reason: d.message })),
        });
      }

      /* eslint-disable @typescript-eslint/no-unsafe-member-access */
      const filters: ICredentialListFilters = {
        page: value.page as number,
        limit: value.limit as number,
        status: value.status as ICredentialListFilters['status'],
      };
      /* eslint-enable @typescript-eslint/no-unsafe-member-access */

      const result = await this.credentialService.listCredentials(agentId, filters);
      res.status(200).json(result);
    } catch (err) {
      next(err);
    }
  };

  /**
   * Handles POST /agents/:agentId/credentials/:credentialId/rotate — rotates a credential.
   * Returns 200 with CredentialWithSecret (new secret shown once only).
   *
   * @param req - Express request.
   * @param res - Express response.
   * @param next - Express next function.
   */
  rotateCredential = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      if (!req.user) {
        throw new AuthenticationError();
      }

      const { agentId, credentialId } = req.params;

      if (req.user.sub !== agentId) {
        throw new AuthorizationError('You do not have permission to manage credentials for this agent.');
      }

      // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
      const { error, value } = generateCredentialSchema.validate(req.body ?? {}, {
        abortEarly: false,
      });
      if (error) {
        throw new ValidationError('Request validation failed.', {
          details: error.details.map((d) => ({ field: d.path.join('.'), reason: d.message })),
        });
      }

      const data = value as IGenerateCredentialRequest;

      const ipAddress = req.ip ?? '0.0.0.0';
      const userAgent = req.headers['user-agent'] ?? 'unknown';

      const result = await this.credentialService.rotateCredential(
        agentId,
        credentialId,
        data,
        ipAddress,
        userAgent,
      );
      res.status(200).json(result);
    } catch (err) {
      next(err);
    }
  };

  /**
   * Handles DELETE /agents/:agentId/credentials/:credentialId — revokes a credential.
   * Returns 204 No Content.
   *
   * @param req - Express request.
   * @param res - Express response.
   * @param next - Express next function.
   */
  revokeCredential = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      if (!req.user) {
        throw new AuthenticationError();
      }

      const { agentId, credentialId } = req.params;

      if (req.user.sub !== agentId) {
        throw new AuthorizationError('You do not have permission to manage credentials for this agent.');
      }

      const ipAddress = req.ip ?? '0.0.0.0';
      const userAgent = req.headers['user-agent'] ?? 'unknown';

      await this.credentialService.revokeCredential(agentId, credentialId, ipAddress, userAgent);
      res.status(204).send();
    } catch (err) {
      next(err);
    }
  };
}
