import { createServer } from 'node:http';
import { resolve } from 'node:path';
import compression from 'compression';
import cookieParser from 'cookie-parser';
import express, { type Express } from 'express';
import session from 'express-session';
import helmet from 'helmet';
import { pinoHttp } from 'pino-http';
import { RedisStore } from 'connect-redis';
import type { AppEnv } from '../config/env.js';
import type { Database } from '../db/types.js';
import { DomainError } from '../domain/errors.js';
import { createRealtimeToken, type RealtimeHub, type RedisBridge } from '../realtime/hub.js';
import { AuthService } from '../services/auth-service.js';
import { DeliveryService } from '../services/delivery-service.js';
import { FinancialImportService } from '../services/excel-import-service.js';
import { InvoiceService } from '../services/invoice-service.js';
import { PushService } from '../services/push-service.js';
import { ReportService } from '../services/report-service.js';
import { UserService } from '../services/user-service.js';
import { adminRoutes } from './admin-routes.js';
import { authRoutes } from './auth-routes.js';
import { ensureCsrf, loadCurrentUser } from './middleware.js';
import { operationalRoutes } from './operational-routes.js';

export interface ApplicationDependencies {
  db: Database;
  env: AppEnv;
  realtime: RealtimeHub;
  redis?: RedisBridge | null;
  logger?: any;
}

export function createApplication(dependencies: ApplicationDependencies): Express {
  const { db, env, realtime } = dependencies;
  const app = express();
  app.disable('x-powered-by');
  app.set('trust proxy', env.TRUST_PROXY);
  app.set('view engine', 'ejs');
  app.set('views', resolve('src/views'));
  app.locals.appName = env.APP_NAME;

  app.use(pinoHttp({ logger: dependencies.logger, redact: ['req.headers.cookie', 'req.headers.authorization', 'req.body.password', 'req.body.currentPassword'] }));
  app.use(helmet({
    contentSecurityPolicy: {
      directives: {
        defaultSrc: ["'self'"], scriptSrc: ["'self'", "'unsafe-eval'"], styleSrc: ["'self'", "'unsafe-inline'"],
        imgSrc: ["'self'", 'data:'], connectSrc: ["'self'", 'ws:', 'wss:'], workerSrc: ["'self'"], objectSrc: ["'none'"],
        scriptSrcAttr: ["'none'"]
      }
    },
    crossOriginEmbedderPolicy: false
  }));
  app.use(compression());
  app.use(express.static(resolve('public'), { maxAge: env.NODE_ENV === 'production' ? '7d' : 0, etag: true }));
  app.use(express.urlencoded({ extended: true, limit: '1mb', parameterLimit: 500 }));
  app.use(express.json({ limit: '256kb' }));
  app.use(cookieParser());
  const store = dependencies.redis ? new RedisStore({ client: dependencies.redis as any, prefix: 'mandegar:session:' }) : undefined;
  app.use(session({
    name: env.SESSION_COOKIE_NAME,
    secret: env.SESSION_SECRET,
    store,
    resave: false,
    saveUninitialized: false,
    rolling: true,
    cookie: { httpOnly: true, secure: env.SECURE_COOKIES, sameSite: 'strict', maxAge: 8 * 60 * 60 * 1000, path: '/' }
  }));
  app.use(ensureCsrf);
  app.use(loadCurrentUser(db));
  app.use((req, res, next) => {
    res.locals.vapidPublicKey = env.VAPID_PUBLIC_KEY;
    res.locals.realtimeToken = req.currentUser ? createRealtimeToken({
      userId: req.currentUser.id, sessionVersion: req.currentUser.sessionVersion, expiresAt: Date.now() + 10 * 60 * 1000
    }, env.APP_KEY) : '';
    next();
  });

  app.get('/health/live', (_req, res) => res.json({ status: 'ok', service: 'web' }));
  app.get('/health/ready', async (_req, res) => {
    try { await db.query('SELECT 1 AS ready'); res.json({ status: 'ready', database: db.kind }); }
    catch { res.status(503).json({ status: 'not-ready' }); }
  });

  const auth = new AuthService(db);
  const push = new PushService(db, env);
  const publisher = async (channel: string, payload: Record<string, unknown>) => {
    await realtime.publish(channel, payload);
    if (channel.startsWith('warehouse:') && payload.requestId) {
      await push.sendToWarehouse(channel.slice('warehouse:'.length), {
        title: 'درخواست تحویل جدید', body: 'یک درخواست تأییدشده جدید در صف انبار قرار گرفت.', url: `/warehouse/${payload.requestId}`
      });
    }
    if (channel.startsWith('user:') && payload.requestId) {
      await push.sendToUser(channel.slice('user:'.length), {
        title: 'به‌روزرسانی تحویل', body: 'وضعیت یک درخواست تحویل تغییر کرد.', url: `/invoices`
      });
    }
  };
  app.use(authRoutes(auth, env));
  app.use(operationalRoutes({
    db, env, deliveries: new DeliveryService(db, publisher), invoices: new InvoiceService(db),
    imports: new FinancialImportService(db), reports: new ReportService(db), push
  }));
  app.use(adminRoutes(db, new UserService(db), auth));

  app.use((_req, res) => res.status(404).render('error', { title: 'صفحه پیدا نشد', status: 404, message: 'صفحه درخواستی پیدا نشد.' }));
  app.use((error: unknown, req: express.Request, res: express.Response, _next: express.NextFunction) => {
    const domain = error instanceof DomainError ? error : null;
    const status = domain?.status ?? 500;
    const message = domain?.message ?? (env.NODE_ENV === 'production' ? 'خطای پیش‌بینی‌نشده‌ای رخ داد.' : (error as Error).message);
    req.log?.error({ err: error, status }, 'request failed');
    if (req.accepts(['html', 'json']) === 'json') return res.status(status).json({ error: domain?.code ?? 'INTERNAL_ERROR', message });
    res.status(status).render('error', { title: status === 403 ? 'دسترسی غیرمجاز' : 'خطا', status, message });
  });
  return app;
}

export function createHttpServer(app: Express, realtime: RealtimeHub) {
  const server = createServer(app);
  realtime.attach(server);
  return server;
}
