import type { FastifyInstance } from 'fastify';
import type { LlmService } from '../services/llm.service.js';
import { LlmAuthVerificationError } from '../services/llm.service.js';
import { NotFoundError, ConflictError } from '../services/mcp-server.service.js';

export function registerLlmRoutes(
  app: FastifyInstance,
  service: LlmService,
): void {
  app.get('/api/v1/llms', async () => {
    return service.list();
  });

  // Accepts either CUID or human name. Used both by the CLI (which usually
  // resolves to CUID first) and by FailoverRouter's RBAC pre-check (which
  // hands over the user-facing name to avoid an extra round-trip).
  app.get<{ Params: { id: string } }>('/api/v1/llms/:id', async (request, reply) => {
    try {
      return await getByIdOrName(service, request.params.id);
    } catch (err) {
      if (err instanceof NotFoundError) {
        reply.code(404);
        return { error: err.message };
      }
      throw err;
    }
  });

  // No explicit HEAD handler: Fastify auto-derives HEAD from GET, which runs
  // the same RBAC hook + lookup and drops the body. That's exactly what
  // FailoverRouter wants for its "can the caller still view this Llm?" probe.

  app.post('/api/v1/llms', async (request, reply) => {
    try {
      // Body field `_skipAuthCheck`: opt-out for offline registration (e.g.
      // wiring config before the upstream is reachable). Stripped from the
      // body before validation.
      const body = (request.body ?? {}) as Record<string, unknown>;
      const skipAuthCheck = body['_skipAuthCheck'] === true;
      delete body['_skipAuthCheck'];
      const row = await service.create(body, { skipAuthCheck });
      reply.code(201);
      return row;
    } catch (err) {
      if (err instanceof ConflictError) {
        reply.code(409);
        return { error: err.message };
      }
      if (err instanceof LlmAuthVerificationError) {
        reply.code(422);
        return { error: err.message, status: err.status };
      }
      throw err;
    }
  });

  app.put<{ Params: { id: string } }>('/api/v1/llms/:id', async (request, reply) => {
    try {
      const body = (request.body ?? {}) as Record<string, unknown>;
      const skipAuthCheck = body['_skipAuthCheck'] === true;
      delete body['_skipAuthCheck'];
      return await service.update(request.params.id, body, { skipAuthCheck });
    } catch (err) {
      if (err instanceof NotFoundError) {
        reply.code(404);
        return { error: err.message };
      }
      if (err instanceof LlmAuthVerificationError) {
        reply.code(422);
        return { error: err.message, status: err.status };
      }
      throw err;
    }
  });

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

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

/**
 * Look up by CUID first; if the input doesn't look like one, fall back to
 * findByName. Lets the same URL serve both `mcpctl describe llm <name>` and
 * the FailoverRouter's name-based RBAC check.
 */
async function getByIdOrName(service: LlmService, idOrName: string) {
  if (CUID_RE.test(idOrName)) {
    return service.getById(idOrName);
  }
  return service.getByName(idOrName);
}
