import express, { type ErrorRequestHandler } from 'express';
import helmet from 'helmet';
import path from 'node:path';
import { ZodError } from 'zod';
import { Prisma, type PrismaClient } from '../generated/prisma/client.js';
import { authSetup } from './auth.js';
import { HttpError } from './errors.js';
import { adminResources, publicResources } from './resources.js';

export function createApp(db: PrismaClient, config: { databaseUrl: string; sessionSecret: string; origin: string; production?: boolean; loginLimit?: number; trustProxy?: number }) {
  const app = express();
  const auth = authSetup(db, { ...config, production: config.production ?? false });
  app.disable('x-powered-by');
  if (config.trustProxy) app.set('trust proxy', config.trustProxy);
  app.use(helmet({ contentSecurityPolicy: { directives: { 'style-src': ["'self'", "'unsafe-inline'"], 'upgrade-insecure-requests': config.production ? [] : null } }, strictTransportSecurity: config.production ? undefined : false }));
  app.use(express.json({ limit: '512kb' }));
  app.use('/api', (_req, res, next) => { res.set('Cache-Control', 'no-store'); next(); });
  app.use('/api/auth', auth.middleware, auth.csrf, auth.router);
  app.use('/api/admin', auth.middleware, auth.requireAdmin, auth.csrf, adminResources(db));
  app.use('/api', publicResources(db));
  app.use('/api', (_req, _res, next) => next(new HttpError(404, 'Endpoint tidak ditemukan.')));
  const publicDir = path.resolve('public');
  const adminDir = path.resolve('dist/admin');
  app.get('/standing-tv.html', (req, res) => {
    const event = req.query.event;
    const room = req.query.room;
    const rotation = typeof req.query.rotate === 'string' ? `?rotate=${encodeURIComponent(req.query.rotate)}` : '';
    if (typeof event === 'string' && /^\d+$/.test(event) && typeof room === 'string') return res.redirect(`/tv/${event}/${encodeURIComponent(room)}${rotation}`);
    const preselect = typeof room === 'string' ? encodeURIComponent(room) : '';
    res.redirect(`/?chooseEvent=1&room=${preselect}`);
  });
  app.get('/tv/:eventId/:roomCode', (_req, res) => res.sendFile(path.join(publicDir, 'standing-tv.html')));
  app.use('/admin', express.static(adminDir, { index: false }));
  app.get(['/admin', '/admin/{*path}'], (_req, res) => res.sendFile(path.join(adminDir, 'index.html')));
  app.use(express.static(publicDir, { index: 'index.html', dotfiles: 'deny' }));
  app.use((_req, res) => res.status(404).send('Halaman tidak ditemukan.'));
  const errors: ErrorRequestHandler = (error, _req, res, _next) => {
    if (error instanceof ZodError) return void res.status(422).json({ error: { message: 'Periksa kembali isian formulir.', fields: error.issues.map(i => ({ path: i.path.join('.'), message: i.message })) } });
    if (error instanceof HttpError) return void res.status(error.status).json({ error: { message: error.message } });
    if (error instanceof Prisma.PrismaClientKnownRequestError) {
      if (error.code === 'P2002') return void res.status(409).json({ error: { message: 'Data atau urutan tersebut sudah digunakan.' } });
      if (error.code === 'P2003') return void res.status(409).json({ error: { message: 'Data masih digunakan atau referensi tidak ditemukan. Hapus penugasan terkait terlebih dahulu.' } });
      if (error.code === 'P2025') return void res.status(404).json({ error: { message: 'Data tidak ditemukan.' } });
      if (error.code === 'P2034') return void res.status(409).json({ error: { message: 'Data sedang diubah pengguna lain. Coba lagi.' } });
    }
    const status = error?.type === 'entity.parse.failed' ? 400 : error?.type === 'entity.too.large' ? 413 : 500;
    if (status === 500) console.error('Request failed:', error instanceof Error ? error.name : 'UnknownError');
    res.status(status).json({ error: { message: status === 500 ? 'Terjadi gangguan server. Silakan coba lagi.' : 'Format permintaan tidak valid.' } });
  };
  app.use(errors);
  return { app, close: auth.close };
}
