/**
 * /api/v1/agents — Agent CRUD.
 *
 * Mirrors `routes/llms.ts` shape: list / get-by-id-or-name / POST / PUT /
 * DELETE. RBAC is enforced by the global hook in `main.ts:mapUrlToPermission`
 * — the resource is `agents`. The chat endpoints live in `agent-chat.ts` and
 * map to `run:agents:<name>`.
 */
import type { FastifyInstance, FastifyRequest } from 'fastify';
import type { AgentService, AgentViewer } from '../services/agent.service.js';
import { NotFoundError, ConflictError } from '../services/mcp-server.service.js';

/** v7: thread the request's RBAC scope into the service so foreign-private rows 404. */
function viewerFromRequest(request: FastifyRequest): AgentViewer | null {
  if (request.userId === undefined || request.rbacScope === undefined) return null;
  return {
    userId: request.userId,
    wildcard: request.rbacScope.isAdmin,
    allowedNames: request.rbacScope.names,
  };
}

export function registerAgentRoutes(
  app: FastifyInstance,
  service: AgentService,
): void {
  app.get('/api/v1/agents', async () => {
    // List filter is applied by the preSerialization hook (visibility +
    // RBAC name-scope); service stays viewer-blind for internal callers.
    return service.list();
  });

  app.get<{ Params: { id: string } }>('/api/v1/agents/:id', async (request, reply) => {
    try {
      return await getByIdOrName(service, request.params.id, viewerFromRequest(request));
    } catch (err) {
      if (err instanceof NotFoundError) {
        reply.code(404);
        return { error: err.message };
      }
      throw err;
    }
  });

  app.post('/api/v1/agents', async (request, reply) => {
    try {
      const ownerId = request.userId ?? 'system';
      const row = await service.create(request.body, ownerId);
      reply.code(201);
      return row;
    } catch (err) {
      if (err instanceof ConflictError) {
        reply.code(409);
        return { error: err.message };
      }
      if (err instanceof NotFoundError) {
        reply.code(400);
        return { error: err.message };
      }
      throw err;
    }
  });

  app.put<{ Params: { id: string } }>('/api/v1/agents/:id', async (request, reply) => {
    try {
      const target = await getByIdOrName(service, request.params.id, viewerFromRequest(request));
      return await service.update(target.id, request.body);
    } catch (err) {
      if (err instanceof NotFoundError) {
        reply.code(404);
        return { error: err.message };
      }
      throw err;
    }
  });

  app.delete<{ Params: { id: string } }>('/api/v1/agents/:id', async (request, reply) => {
    try {
      const target = await getByIdOrName(service, request.params.id, viewerFromRequest(request));
      await service.delete(target.id);
      reply.code(204);
      return null;
    } catch (err) {
      if (err instanceof NotFoundError) {
        reply.code(404);
        return { error: err.message };
      }
      throw err;
    }
  });

  // GET /api/v1/projects/:projectName/agents — used by mcplocal's agents
  // plugin to enumerate agents for the bound project. Matches the existing
  // /projects/:p/servers endpoint convention.
  app.get<{ Params: { projectName: string } }>(
    '/api/v1/projects/:projectName/agents',
    async (request, reply) => {
      try {
        return await service.listByProject(request.params.projectName, viewerFromRequest(request));
      } catch (err) {
        if (err instanceof NotFoundError) {
          reply.code(404);
          return { error: err.message };
        }
        throw err;
      }
    },
  );
}

const CUID_RE = /^c[a-z0-9]{24}/i;

async function getByIdOrName(service: AgentService, idOrName: string, viewer: AgentViewer | null = null) {
  if (CUID_RE.test(idOrName)) {
    return service.getById(idOrName, viewer);
  }
  return service.getByName(idOrName, viewer);
}
