/**
 * Factory for creating a Fastify service app with standard middleware.
 *
 * Includes: CORS, x-request-id propagation, health endpoint, ServiceError handler.
 */

import { randomUUID } from 'node:crypto';
import Fastify from 'fastify';
import cors from '@fastify/cors';
import { ServiceError } from '@bytelyst/errors';
import type { ServiceAppOptions, FastifyApp } from './types.js';

/**
 * Create a Fastify app preconfigured with common middleware.
 *
 * @example
 * ```ts
 * const app = await createServiceApp({
 *   name: "platform-service",
 *   version: "0.1.0",
 *   description: "Auth, audit, flags, notifications",
 *   corsOrigin: process.env.CORS_ORIGIN,
 * });
 * await app.register(authRoutes, { prefix: "/api" });
 * await startService(app, { port: 4003 });
 * ```
 */
export async function createServiceApp(options: ServiceAppOptions): Promise<FastifyApp> {
  const {
    name,
    version,
    description,
    corsOrigin,
    logger = true,
    logLevel,
    swagger,
    metrics,
    readiness,
    errorResponses,
    optionalPluginFailure = 'warn',
  } = options;

  const app = Fastify({
    logger: logger ? (logLevel ? { level: logLevel } : true) : false,
  }) as unknown as FastifyApp;

  let readyState = typeof readiness === 'object' ? (readiness.initialReady ?? false) : false;
  const readinessPath = typeof readiness === 'object' ? (readiness.path ?? '/ready') : '/ready';
  const includeRequestIdInErrors = errorResponses?.includeRequestId ?? true;

  app.setReadyState = (ready: boolean) => {
    readyState = ready;
  };

  app.isReadyState = () => readyState;

  // CORS — deny all origins when CORS_ORIGIN is not explicitly set
  const origin = corsOrigin ? corsOrigin.split(',').map(o => o.trim()) : false;
  await app.register(cors, { origin });

  // OpenAPI spec (optional — consumer must have @fastify/swagger installed)
  if (swagger) {
    try {
      const swaggerPlugin = (await import('@fastify/swagger')).default;
      await app.register(swaggerPlugin, {
        openapi: {
          info: {
            title: swagger.title,
            version,
            ...(swagger.description && { description: swagger.description }),
          },
          ...(swagger.port && { servers: [{ url: `http://localhost:${swagger.port}` }] }),
        },
      });
    } catch (error) {
      if (optionalPluginFailure === 'throw') {
        throw error;
      }
      app.log.warn(
        { err: error },
        'Optional plugin @fastify/swagger not available; skipping OpenAPI registration'
      );
    }

    // Swagger UI — serves interactive API docs at /documentation
    try {
      const swaggerUiPlugin = (await import('@fastify/swagger-ui')).default;
      await app.register(swaggerUiPlugin, {
        routePrefix: '/documentation',
        uiConfig: { docExpansion: 'list', deepLinking: true },
      });
    } catch (error) {
      if (optionalPluginFailure === 'throw') {
        throw error;
      }
      app.log.warn(
        { err: error },
        'Optional plugin @fastify/swagger-ui not available; skipping Swagger UI registration'
      );
    }
  }

  // Prometheus metrics (optional — consumer must have fastify-metrics installed)
  if (metrics) {
    try {
      const metricsMod = await import('fastify-metrics');
      const plugin = metricsMod.default as unknown as Parameters<typeof app.register>[0];
      await app.register(plugin, { endpoint: '/metrics' });
    } catch (error) {
      if (optionalPluginFailure === 'throw') {
        throw error;
      }
      app.log.warn(
        { err: error },
        'Optional plugin fastify-metrics not available; skipping metrics registration'
      );
    }
  }

  // x-request-id propagation
  app.addHook('onRequest', async (req, reply) => {
    const requestId = (req.headers['x-request-id'] as string) || randomUUID();
    req.headers['x-request-id'] = requestId;
    reply.header('x-request-id', requestId);
    req.log = req.log.child({ requestId });
  });

  // Health check
  app.get('/health', async req => ({
    status: 'ok',
    service: name,
    version,
    ...(description && { description }),
    timestamp: new Date().toISOString(),
    requestId: req.headers['x-request-id'],
  }));

  if (readiness) {
    app.get(readinessPath, async (req, reply) => {
      if (!app.isReadyState()) {
        reply.code(503);
      }

      return {
        status: app.isReadyState() ? 'ready' : 'not_ready',
        service: name,
        version,
        ...(description && { description }),
        timestamp: new Date().toISOString(),
        requestId: req.headers['x-request-id'],
      };
    });
  }

  // ServiceError-aware error handler
  app.setErrorHandler((error, req, reply) => {
    if (error instanceof ServiceError) {
      const body: Record<string, unknown> = { error: error.message };
      if (error.details) body.details = error.details;
      if (includeRequestIdInErrors) body.requestId = req.headers['x-request-id'];
      reply.code(error.statusCode).send(body);
      return;
    }
    app.log.error(error);
    const body: Record<string, unknown> = { error: 'Internal server error' };
    if (includeRequestIdInErrors) body.requestId = req.headers['x-request-id'];
    reply.code(500).send(body);
  });

  return app;
}
