/**
 * Agent Controller for SentryAgent.ai AgentIdP.
 * HTTP handlers for all 5 agent endpoints. No business logic — delegates to AgentService.
 */

import { Request, Response, NextFunction } from 'express';
import { AgentService } from '../services/AgentService.js';
import {
  createAgentSchema,
  updateAgentSchema,
  listAgentsQuerySchema,
} from '../utils/validators.js';
import { ValidationError, AuthorizationError } from '../utils/errors.js';
import {
  ICreateAgentRequest,
  IUpdateAgentRequest,
  IAgentListFilters,
} from '../types/index.js';

/**
 * Controller for the Agent Registry endpoints.
 * Receives AgentService via constructor injection.
 */
export class AgentController {
  /**
   * @param agentService - The agent registry service.
   */
  constructor(private readonly agentService: AgentService) {}

  /**
   * Handles POST /agents — registers a new agent.
   *
   * @param req - Express request with CreateAgentRequest body.
   * @param res - Express response.
   * @param next - Express next function.
   */
  registerAgent = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      if (!req.user) {
        throw new AuthorizationError();
      }

      // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
      const { error, value } = createAgentSchema.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 ICreateAgentRequest;
      const ipAddress = req.ip ?? '0.0.0.0';
      const userAgent = req.headers['user-agent'] ?? 'unknown';

      const agent = await this.agentService.registerAgent(data, ipAddress, userAgent);
      res.status(201).json(agent);
    } catch (err) {
      next(err);
    }
  };

  /**
   * Handles GET /agents — returns a paginated list of agents.
   *
   * @param req - Express request with optional query filters.
   * @param res - Express response.
   * @param next - Express next function.
   */
  listAgents = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      if (!req.user) {
        throw new AuthorizationError();
      }

      // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
      const { error, value } = listAgentsQuerySchema.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: IAgentListFilters = {
        page: value.page as number,
        limit: value.limit as number,
        owner: value.owner as string | undefined,
        agentType: value.agentType as IAgentListFilters['agentType'],
        status: value.status as IAgentListFilters['status'],
      };
      /* eslint-enable @typescript-eslint/no-unsafe-member-access */

      const result = await this.agentService.listAgents(filters);
      res.status(200).json(result);
    } catch (err) {
      next(err);
    }
  };

  /**
   * Handles GET /agents/:agentId — retrieves a single agent.
   *
   * @param req - Express request with agentId path param.
   * @param res - Express response.
   * @param next - Express next function.
   */
  getAgentById = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      if (!req.user) {
        throw new AuthorizationError();
      }

      const { agentId } = req.params;
      const agent = await this.agentService.getAgentById(agentId);
      res.status(200).json(agent);
    } catch (err) {
      next(err);
    }
  };

  /**
   * Handles PATCH /agents/:agentId — partially updates an agent.
   *
   * @param req - Express request with agentId path param and UpdateAgentRequest body.
   * @param res - Express response.
   * @param next - Express next function.
   */
  updateAgent = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      if (!req.user) {
        throw new AuthorizationError();
      }

      // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
      const { error, value } = updateAgentSchema.validate(req.body, { abortEarly: false });
      if (error) {
        const immutableFields = ['agentId', 'email', 'createdAt'];
        const firstImmutable = error.details.find((d) =>
          immutableFields.includes(d.path[0] as string),
        );
        if (firstImmutable) {
          throw new ValidationError(`The field '${String(firstImmutable.path[0])}' cannot be modified after registration.`, {
            field: firstImmutable.path[0],
          });
        }
        throw new ValidationError('Request validation failed.', {
          details: error.details.map((d) => ({ field: d.path.join('.'), reason: d.message })),
        });
      }

      const { agentId } = req.params;
      /* eslint-disable @typescript-eslint/no-unsafe-member-access */
      const data: IUpdateAgentRequest = {
        agentType: value.agentType as IUpdateAgentRequest['agentType'],
        version: value.version as string | undefined,
        capabilities: value.capabilities as string[] | undefined,
        owner: value.owner as string | undefined,
        deploymentEnv: value.deploymentEnv as IUpdateAgentRequest['deploymentEnv'],
        status: value.status as IUpdateAgentRequest['status'],
        isPublic: value.isPublic as boolean | undefined,
      };
      /* eslint-enable @typescript-eslint/no-unsafe-member-access */
      const ipAddress = req.ip ?? '0.0.0.0';
      const userAgent = req.headers['user-agent'] ?? 'unknown';

      const updated = await this.agentService.updateAgent(agentId, data, ipAddress, userAgent);
      res.status(200).json(updated);
    } catch (err) {
      next(err);
    }
  };

  /**
   * Handles DELETE /agents/:agentId — decommissions an agent.
   *
   * @param req - Express request with agentId path param.
   * @param res - Express response (204 No Content).
   * @param next - Express next function.
   */
  decommissionAgent = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      if (!req.user) {
        throw new AuthorizationError();
      }

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

      await this.agentService.decommissionAgent(agentId, ipAddress, userAgent);
      res.status(204).send();
    } catch (err) {
      next(err);
    }
  };
}
