/**
 * نقطه ورود سرور.
 * لایه‌های امنیتی: Helmet (CSP)، CORS محدود، Rate Limit، CSRF، کوکی امضاشده،
 * محدودیت اندازه بدنه و مدیریت متمرکز خطا با پیام‌های فارسی.
 */
import Fastify from 'fastify';
import cors from '@fastify/cors';
import helmet from '@fastify/helmet';
import cookie from '@fastify/cookie';
import rateLimit from '@fastify/rate-limit';
import multipart from '@fastify/multipart';
import { ZodError } from 'zod';
import { config } from './config/index.js';
import { initDatabase } from './database/client.js';
import { AppError } from './common/errors.js';
import { attachPrincipal, csrfGuard } from './guards/index.js';
import authRoutes from './http/routes/auth.js';
import publicRoutes from './http/routes/public.js';
import searchRoutes, { runSavedSearchSweep } from './http/routes/search.js';
import editorialRoutes from './http/routes/editorial.js';
import civicRoutes from './http/routes/civic.js';
import adminRoutes from './http/routes/admin.js';
import userRoutes, { mediaRoutes } from './http/routes/user.js';
import { runSlaSweep } from './modules/reports/service.js';
import { runEditorialMaintenance } from './modules/articles/service.js';
import { retryFailed } from './modules/communications/service.js';

export async function buildServer() {
  const app = Fastify({
    logger: config.env === 'development' ? { transport: { target: 'pino-pretty', options: { translateTime: 'HH:MM:ss' } } } : true,
    bodyLimit: 2 * 1024 * 1024,
    trustProxy: true,
    // نامک‌های فارسی پس از URL-Encode طولانی می‌شوند؛ سقف پیش‌فرض ۱۰۰ نویسه کافی نیست.
    maxParamLength: 600,
  });

  await app.register(helmet, {
    contentSecurityPolicy: {
      directives: {
        defaultSrc: ["'self'"],
        imgSrc: ["'self'", 'data:', 'blob:', 'https:'],
        mediaSrc: ["'self'", 'blob:', 'https:'],
        scriptSrc: ["'self'"],
        styleSrc: ["'self'", "'unsafe-inline'"],
        connectSrc: ["'self'"],
        frameAncestors: ["'self'"],
        objectSrc: ["'none'"],
      },
    },
    crossOriginResourcePolicy: { policy: 'cross-origin' },
  });

  // CORS: فقط مبدأهای مجاز؛ در محیط توسعه، پیش‌نمایش سندباکس هم مجاز است
  await app.register(cors, {
    origin: (origin, cb) => {
      if (!origin) return cb(null, true);
      const allowed = /^(http:\/\/localhost:\d+|https:\/\/[\w-]+\.e2b\.app)$/.test(origin);
      cb(null, allowed);
    },
    credentials: true,
  });

  await app.register(cookie, { secret: process.env.COOKIE_SECRET ?? 'dev-only-secret-change-in-production' });
  await app.register(rateLimit, { max: 300, timeWindow: '1 minute' });
  await app.register(multipart, { limits: { fileSize: config.security.maxUploadBytes, files: 5 } });

  app.addHook('onRequest', attachPrincipal);
  app.addHook('onRequest', csrfGuard);

  app.setErrorHandler((error, req, reply) => {
    if (error instanceof ZodError) {
      return reply.status(422).send({
        error: 'VALIDATION_ERROR',
        message: 'داده ارسالی معتبر نیست.',
        details: error.issues.map((i) => ({ path: i.path.join('.'), message: i.message })),
      });
    }
    if (error instanceof AppError) {
      return reply.status(error.statusCode).send({ error: error.code, message: error.message, details: error.details });
    }
    if ((error as { statusCode?: number }).statusCode === 429) {
      return reply.status(429).send({ error: 'RATE_LIMITED', message: 'تعداد درخواست‌ها بیش از حد مجاز است.' });
    }
    req.log.error(error);
    return reply.status(500).send({ error: 'INTERNAL_ERROR', message: 'خطای داخلی سرور رخ داد.' });
  });

  app.get('/api/health', async () => ({ ok: true, time: new Date().toISOString() }));

  await app.register(authRoutes, { prefix: '/api/auth' });
  await app.register(publicRoutes, { prefix: '/api' });
  await app.register(searchRoutes, { prefix: '/api/search' });
  await app.register(editorialRoutes, { prefix: '/api/editorial/articles' });
  await app.register(civicRoutes, { prefix: '/api/civic' });
  await app.register(adminRoutes, { prefix: '/api/admin' });
  await app.register(userRoutes, { prefix: '/api/me' });
  await app.register(mediaRoutes, { prefix: '/api/media' });

  return app;
}

/**
 * زمان‌بند داخلی.
 * در محیط Data Center این کارها به Worker جداگانه با Redis/BullMQ منتقل می‌شوند؛
 * واسط یکسان است تا مهاجرت بدون تغییر منطق دامنه انجام شود.
 */
function startScheduler(log: { info: (o: unknown, m?: string) => void }) {
  const every = (ms: number, name: string, fn: () => Promise<unknown>) =>
    setInterval(() => {
      fn().then((result) => log.info({ job: name, result })).catch((e) => log.info({ job: name, error: String(e) }));
    }, ms).unref();

  every(5 * 60_000, 'sla-sweep', runSlaSweep);
  every(10 * 60_000, 'editorial-lock', runEditorialMaintenance);
  every(15 * 60_000, 'saved-search', runSavedSearchSweep);
  every(5 * 60_000, 'communication-retry', () => retryFailed());
}

const isMain = process.argv[1]?.endsWith('main.ts') || process.argv[1]?.endsWith('main.js');
if (isMain) {
  const app = await buildServer();
  await initDatabase();
  startScheduler(app.log as never);
  await app.listen({ port: config.port, host: config.host });
}
