/**
 * Fastify Service Routes Generator
 *
 * Generates HTTP route handlers for service operations declared in
 * the spec. The convention matches app-demo's dynamic runtime
 * (src/api/http-server.ts:611) so smoke-parity tests can hit both
 * backends with the same URL:
 *
 *   POST /api/services/{ServiceName}/{operationName}
 *
 * Both segments are camelCase / PascalCase as written in the spec —
 * NO kebab-case transform. This matches:
 *   1. App-demo's URL exactly (the lookup is by literal name)
 *   2. The custom-action convention used by controller routes
 *      (controllers/templates/fastify/routes-generator.ts:48 — paths
 *      use `actionName` verbatim)
 *
 * The generated routes file accepts a `services` map in `options`
 * and dispatches to the named operation method on the service
 * instance. Service classes are emitted by
 * services/templates/prisma/service-generator.ts. The parent prefix
 * `/api/services/{ServiceName}` is set when main.ts registers the
 * routes file.
 */

import type { TemplateContext } from '@specverse/types';

export default function generateFastifyServiceRoutes(context: TemplateContext): string {
  const { service } = context;

  if (!service) {
    throw new Error('Service is required in template context');
  }

  const serviceName = service.name;
  if (!serviceName) {
    throw new Error('Service must have a name');
  }

  // Normalize operations: object form `{ getResults: {...} }` is the
  // canonical shape, but we also accept array form `[{name, ...}]`.
  const operationEntries: Array<[string, any]> = Array.isArray(service.operations)
    ? service.operations.map((op: any) => [op.name, op])
    : Object.entries(service.operations || {});

  if (operationEntries.length === 0) {
    return `/**
 * ${serviceName} — no operations declared, no routes generated.
 */
import type { FastifyInstance } from 'fastify';

export default async function ${serviceName}Routes(_fastify: FastifyInstance, _options: any) {
  // No operations declared on ${serviceName}.
}
`;
  }

  const routeHandlers = operationEntries
    .map(([opName, op]) => generateOperationRoute(opName, op, serviceName))
    .join('\n\n');

  return `import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';

/**
 * ${serviceName} Routes
 *
 * Generated from SpecVerse specification — exposes service operations
 * as HTTP endpoints under /api/services/${serviceName}/.
 *
 * Operations: ${operationEntries.map(([n]) => n).join(', ')}
 */
export default async function ${serviceName}Routes(
  fastify: FastifyInstance,
  options: any
) {
  const service = options.services.${serviceName};
  if (!service) {
    throw new Error('Service ${serviceName} not registered in options.services');
  }

${routeHandlers.split('\n').map(line => '  ' + line).join('\n')}
}
`;
}

/**
 * Generate one route handler for a service operation.
 * URL: POST /{operationName}  (camelCase, no transform)
 * The parent prefix /api/services/{ServiceName} is set by main.ts.
 */
function generateOperationRoute(opName: string, op: any, serviceName: string): string {
  const params = op?.parameters || {};
  const paramNames = Object.keys(params);

  // Build per-parameter validation. Same shape as routes-generator's
  // custom-action validation: required + type checks, inline errors[].
  const validations: string[] = [];
  for (const [pName, pDef] of Object.entries(params) as [string, any][]) {
    const required = typeof pDef === 'string' ? pDef.includes('required') : pDef?.required;
    const typeStr = typeof pDef === 'string' ? pDef.split(' ')[0] : pDef?.type || 'String';
    if (required) {
      validations.push(`  if (body.${pName} === undefined || body.${pName} === null) errors.push({ field: '${pName}', message: '${pName} is required' });`);
    }
    if (typeStr === 'UUID' || typeStr === 'String' || typeStr === 'Email') {
      validations.push(`  if (body.${pName} !== undefined && typeof body.${pName} !== 'string') errors.push({ field: '${pName}', message: '${pName} must be a string' });`);
    } else if (typeStr === 'Integer' || typeStr === 'Number' || typeStr === 'Float') {
      validations.push(`  if (body.${pName} !== undefined && typeof body.${pName} !== 'number') errors.push({ field: '${pName}', message: '${pName} must be a number' });`);
    } else if (typeStr === 'Boolean') {
      validations.push(`  if (body.${pName} !== undefined && typeof body.${pName} !== 'boolean') errors.push({ field: '${pName}', message: '${pName} must be a boolean' });`);
    }
  }

  const validationBlock = validations.length > 0
    ? `  const errors: Array<{ field: string; message: string }> = [];\n${validations.join('\n')}\n  if (errors.length > 0) return reply.status(400).send({ error: 'Validation failed', details: errors });`
    : '';

  // Service operations either take individual params or an options
  // object. Default to passing the whole body — matches the service
  // class signature `public async getResults(params: any)` from
  // service-generator.ts when no parameters are declared.
  const callExpression = paramNames.length > 0
    ? `service.${opName}(${paramNames.map(p => `body.${p}`).join(', ')})`
    : `service.${opName}(body)`;

  return `// ${opName}${op?.description ? ` — ${op.description}` : ''}
fastify.post('/${opName}', {
  handler: async (request: FastifyRequest, reply: FastifyReply) => {
    try {
      const body = (request.body || {}) as Record<string, any>;
${validationBlock}
      const result = await ${callExpression};
      return reply.send(result ?? { success: true });
    } catch (error) {
      return reply.status(400).send({
        error: 'Failed to execute ${serviceName}.${opName}',
        message: error instanceof Error ? error.message : String(error)
      });
    }
  }
});`;
}

