import Fastify from 'fastify';
import cors from '@fastify/cors';
import helmet from '@fastify/helmet';
import compress from '@fastify/compress';
import swagger from '@fastify/swagger';
import swaggerUi from '@fastify/swagger-ui';
import { fastifyAwilixPlugin } from '@fastify/awilix';
import {
  serializerCompiler,
  validatorCompiler,
  jsonSchemaTransform,
  hasZodFastifySchemaValidationErrors,
  isResponseSerializationError,
} from 'fastify-type-provider-zod';
import type { ApiError } from '@meshitrack/shared';
import config from './config/configuration.js';
import { AppError } from './common/errors.js';

// Import types to enable declaration merging
import './common/types.js';

// Import plugins
import mongoosePlugin from './plugins/mongoose.plugin.js';
import authPlugin from './plugins/auth.plugin.js';
import householdPlugin from './plugins/household.plugin.js';

// Import route modules
import healthRoutes from './modules/health/health.routes.js';
import usersRoutes from './modules/users/users.routes.js';
import householdsRoutes from './modules/households/households.routes.js';
import medicinesRoutes from './modules/medicines/medicines.routes.js';
import medicineProductsRoutes from './modules/medicine-products/medicine-products.routes.js';
import cabinetRoutes from './modules/cabinet/cabinet.routes.js';
import cabinetEventsRoutes from './modules/cabinet-events/cabinet-events.routes.js';
import regimensRoutes from './modules/regimens/regimens.routes.js';
import organizerRoutes from './modules/organizer/organizer.routes.js';
import storesRoutes from './modules/stores/stores.routes.js';
import medicinePricesRoutes from './modules/medicine-prices/medicine-prices.routes.js';
import refillsRoutes from './modules/refills/refills.routes.js';
import purchasesRoutes from './modules/purchases/purchases.routes.js';
import recipesRoutes from './modules/recipes/recipes.routes.js';
import pantryRoutes from './modules/pantry/pantry.routes.js';
import freshnessRulesRoutes from './modules/freshness-rules/freshness-rules.routes.js';
import productsRoutes from './modules/products/products.routes.js';
import mealPlansRoutes from './modules/meal-plans/meal-plans.routes.js';
import nutritionTargetsRoutes from './modules/nutrition-targets/nutrition-target.routes.js';
import websocket from '@fastify/websocket';
import pricesRoutes from './modules/prices/prices.routes.js';
import shoppingListsRoutes from './modules/shopping-lists/shopping-lists.routes.js';

export async function buildApp(opts: { logger?: boolean | object } = {}) {
  const app = Fastify({
    logger: opts.logger ?? {
      level: 'info',
      ...(process.env['NODE_ENV'] !== 'production' ? { transport: { target: 'pino-pretty' } } : {}),
    },
  });

  // Zod type provider
  app.setValidatorCompiler(validatorCompiler);
  app.setSerializerCompiler(serializerCompiler);

  // Security & compression
  await app.register(helmet, {
    crossOriginResourcePolicy: { policy: 'cross-origin' },
    contentSecurityPolicy: false,
  });
  await app.register(cors, {
    origin: config.cors.origin,
    credentials: true,
    methods: ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE'],
    allowedHeaders: ['Content-Type', 'Authorization'],
  });
  await app.register(compress);

  // Swagger / OpenAPI
  await app.register(swagger, {
    openapi: {
      info: {
        title: 'MeshiTrack API',
        description: 'Nutrition & Pantry Management Platform',
        version: '0.0.1',
      },
      servers: [{ url: `http://localhost:${config.port}` }],
      components: {
        securitySchemes: {
          bearerAuth: {
            type: 'http',
            scheme: 'bearer',
            bearerFormat: 'JWT',
          },
        },
      },
      security: [{ bearerAuth: [] }],
    },
    transform: jsonSchemaTransform,
  });

  await app.register(swaggerUi, { routePrefix: '/api/docs' });

  // DI container (Awilix)
  await app.register(fastifyAwilixPlugin, {
    disposeOnClose: true,
    disposeOnResponse: true,
    strictBooleanEnforced: true,
  });

  // Database
  await app.register(mongoosePlugin);

  // Auth & household guards
  await app.register(authPlugin);
  await app.register(householdPlugin);

  // WebSocket Plugin for persistent synchronizations
  await app.register(websocket);

  // Route modules
  await app.register(healthRoutes);
  await app.register(usersRoutes);
  await app.register(householdsRoutes);
  await app.register(medicinesRoutes);
  await app.register(medicineProductsRoutes);
  await app.register(cabinetRoutes);
  await app.register(cabinetEventsRoutes);
  await app.register(regimensRoutes);
  await app.register(organizerRoutes);
  await app.register(storesRoutes);
  await app.register(medicinePricesRoutes);
  await app.register(refillsRoutes);
  await app.register(purchasesRoutes);
  await app.register(productsRoutes);
  await app.register(recipesRoutes);
  await app.register(pantryRoutes);
  await app.register(freshnessRulesRoutes);
  await app.register(mealPlansRoutes);
  await app.register(nutritionTargetsRoutes);
  await app.register(pricesRoutes);
  await app.register(shoppingListsRoutes);

  // Global error handler
  app.setErrorHandler((error, request, reply) => {
    /* v8 ignore start -- Zod validation errors (tested via integration/E2E) */
    if (hasZodFastifySchemaValidationErrors(error)) {
      const body: ApiError = {
        statusCode: 400,
        error: 'Validation Error',
        message: 'Request validation failed',
        details: formatZodIssues(error.validation),
        timestamp: new Date().toISOString(),
        path: request.url,
      };
      return reply.status(400).send(body);
    }
    /* v8 ignore stop */

    /* v8 ignore start -- Response serialization errors (tested via integration/E2E) */
    if (isResponseSerializationError(error)) {
      request.log.error(error, 'Response serialization error');
      const body: ApiError = {
        statusCode: 500,
        error: 'Internal Server Error',
        message: 'Response validation failed',
        timestamp: new Date().toISOString(),
        path: request.url,
      };
      return reply.status(500).send(body);
    }
    /* v8 ignore stop */

    // Application errors (our custom error classes)
    if (error instanceof AppError) {
      const body: ApiError = {
        statusCode: error.statusCode,
        error: error.error,
        message: error.message,
        details: error.details,
        timestamp: new Date().toISOString(),
        path: request.url,
      };
      return reply.status(error.statusCode).send(body);
    }

    // Fastify-level errors (e.g., 404 from routing)
    const fastifyError = error as { statusCode?: number; name?: string; message?: string };
    const statusCode = fastifyError.statusCode ?? 500;
    if (statusCode >= 500) {
      request.log.error(error, 'Unhandled error');
    }

    const body: ApiError = {
      statusCode,
      error: statusCode >= 500 ? 'Internal Server Error' : (fastifyError.name ?? 'Error'),
      message:
        statusCode >= 500
          ? 'An unexpected error occurred'
          : (fastifyError.message ?? 'Unknown error'),
      timestamp: new Date().toISOString(),
      path: request.url,
    };
    return reply.status(statusCode).send(body);
  });

  return app;
}

/* v8 ignore start -- called only from Zod validation handler above */
function formatZodIssues(issues: unknown[]): Record<string, string[]> {
  const result: Record<string, string[]> = {};
  for (const issue of issues) {
    const zodIssue = issue as {
      params?: { issue?: { path?: (string | number)[]; message?: string } };
    };
    const path = zodIssue.params?.issue?.path?.join('.') || '_root';
    const message = zodIssue.params?.issue?.message || 'Validation failed';
    if (!result[path]) result[path] = [];
    result[path].push(message);
  }
  return result;
}
/* v8 ignore stop */

/* v8 ignore start -- entry-point bootstrap, tested via integration/E2E */
const isMain = process.argv[1] && import.meta.url.endsWith(process.argv[1].replace(/\\/g, '/'));
if (isMain || process.argv[1]?.endsWith('main.js') || process.argv[1]?.endsWith('main.ts')) {
  const app = await buildApp();
  try {
    await app.listen({ port: config.port, host: '0.0.0.0' });
  } catch (err) {
    app.log.fatal(err);
    process.exit(1);
  }
}
/* v8 ignore stop */
