import { serve } from '@hono/node-server';
import { serveStatic } from '@hono/node-server/serve-static';
import { Hono } from 'hono';
import { secureHeaders } from 'hono/secure-headers';
import { bodyLimit } from 'hono/body-limit';

import { rateLimit } from './ratelimit.js';
import content from './routes/content.js';
import preview from './routes/preview.js';
import publish from './routes/publish.js';
import upload from './routes/upload.js';
import profile from './routes/profile.js';
import users from './routes/users.js';
import stats from './routes/stats.js';
import system from './routes/system.js';
import account from './routes/account.js';
import history from './routes/history.js';
import authRoute from './routes/auth.js';
import { requireAuth, requireAdmin } from './auth.js';
import { config } from './config.js';
import { supabase } from './supabase.js';
import { manager } from './plugins.js';
import { runMigrations } from './migrate.js';

// Fail-fast: auth:'supabase' ohne erreichbaren Service-Client ist ein Fehlstart.
// (auth:'local' braucht keinen — DB-loser Betrieb.)
if (config.auth === 'supabase' && !supabase) {
  console.error('FEHLT: SUPABASE_URL/SERVICE_KEY für auth: supabase');
  process.exit(1);
}

const SITE_DIR = process.env.SITE_DIR || '/site';
const ADMIN_DIR = process.env.ADMIN_DIR || '/app/admin-dist';
const PORT = Number(process.env.PORT || 3000);

const app = new Hono();

// --- Sicherheits-Header (auf allem) ---
// CSP bewusst zurückhaltend: Site + Admin-SPA + Dialog-Widget laufen same-origin.
app.use('*', secureHeaders({
  xFrameOptions: 'SAMEORIGIN',
  xContentTypeOptions: 'nosniff',
  referrerPolicy: 'strict-origin-when-cross-origin',
  crossOriginOpenerPolicy: 'same-origin',
  // HSTS nur sinnvoll hinter TLS-Proxy; schadet via HTTP nicht (Browser ignoriert).
  strictTransportSecurity: 'max-age=31536000; includeSubDomains',
}));

// Hochgeladene Bilder strikt isolieren: ein bösartiges SVG kann so kein
// JavaScript im Origin ausführen (sandbox + keine Skript-Quellen).
app.use('/images/*', secureHeaders({
  contentSecurityPolicy: { defaultSrc: ["'none'"], imgSrc: ["'self'"], styleSrc: ["'unsafe-inline'"], sandbox: [] },
  xContentTypeOptions: 'nosniff',
}));

// Statische Assets cachen: Hugo fingerprintet CSS/JS, Uploads haben stabile,
// eindeutige Namen. HTML bleibt ungecacht (Antwort ohne Header → immer frisch).
app.use('*', async (c, next) => {
  await next();
  if (c.req.method === 'GET' && /\.(css|js|mjs|woff2?|ttf|otf|eot|svg|png|jpe?g|webp|avif|gif|ico)$/i.test(c.req.path)) {
    c.header('Cache-Control', 'public, max-age=604800');   // 1 Woche
  }
});

// --- API ---
// Globales Limit gegen aufgeblähte JSON-Bodies (DoS / DB-Bloat). Der Upload-Pfad
// ist ausgenommen — der bringt sein eigenes, größeres Bild-Limit mit.
const jsonBodyLimit = bodyLimit({ maxSize: 256 * 1024, onError: (c) => c.json({ error: 'Anfrage zu groß' }, 413) });
app.use('/api/*', (c, next) =>
  c.req.path.startsWith('/api/upload') ? next() : jsonBodyLimit(c, next));

app.get('/api/health', (c) => c.json({ ok: true, hugo: '0.161.1+extended' }));
// Öffentliche Plugin-Routen (ohne Login) — je nach config.plugins, z. B. Dialog
// lesen + Widget-Login. Jede Route bringt ihre eigenen Limits mit (dialog drosselt
// den Login auf 10 Versuche/IP pro 5 Minuten). Pluginlose Site: nichts gemountet.
manager.mountPublic(app);
// Lokaler Login (nur auth: 'local') — ersetzt GoTrue, öffentlich + gedrosselt.
// Supabase-Sites loggen direkt gegen GoTrue ein und brauchen das nicht.
if (config.auth === 'local') app.route('/api/auth', authRoute);
// Öffentlich: Versionsverlauf der Beiträge (Git-History) — auf der Site anzeigbar.
app.route('/api/history', history);
// Alles weitere unter /api/* braucht ein gültiges Token (Supabase oder lokal).
app.use('/api/*', requireAuth);
// Schreibzugriffe drosseln (Spam-Schutz, auch bei gekapertem Token):
// 60 Mutationen/Minute je Nutzer. Lesen (GET) bleibt frei.
const mutateLimit = rateLimit({
  max: 60, windowMs: 60_000,
  keyFn: (c) => 'u:' + (c.get('user')?.id || c.req.header('x-forwarded-for') || 'anon'),
});
app.use('/api/*', (c, next) => (c.req.method === 'GET' ? next() : mutateLimit(c, next)));
app.get('/api/me', (c) => c.json({ email: c.get('email'), role: c.get('role'), isAdmin: c.get('isAdmin'), canModerate: c.get('canModerate') }));
// Authentifizierte Plugin-Routen (nach requireAuth). admin:true-Routen laufen
// hinter requireAdmin; self-guarding Sub-Apps (dialog mod/adminForums) regeln den
// Zugriff selbst und brauchen das Flag nicht.
manager.mountPrivate(app, '/api', requireAdmin);
app.route('/api/content', content);
app.route('/api/preview', preview);
app.route('/api/publish', publish);
app.route('/api/upload', upload);
app.route('/api/profile', profile);
app.route('/api/users', users);
app.route('/api/stats', stats);
app.route('/api/system', system);
app.route('/api/account', account);
// Content-Modell der Site fürs Admin (schema-getriebenes Rendern). Jede:r
// eingeloggte Nutzer:in darf es lesen — der Editor braucht es zum Aufbauen.
app.get('/api/schema', (c) => c.json({
  site: config.site || null,
  auth: config.auth || 'supabase',
  plugins: config.plugins,
  collections: config.collections,
}));

// --- Admin-SPA (im Container mitgebaut, unter /admin serviert) ---
app.get('/admin', (c) => c.redirect('/admin/'));
app.use(
  '/admin/*',
  serveStatic({
    root: ADMIN_DIR,
    rewriteRequestPath: (p) => p.replace(/^\/admin/, '') || '/',
  }),
);

// --- Vorschau (gebaut nach preview/ mit --buildDrafts) ---
app.use(
  '/_preview/*',
  serveStatic({
    root: `${SITE_DIR}/preview`,
    rewriteRequestPath: (p) => p.replace(/^\/_preview/, ''),
  }),
);

// Hochgeladene Bilder direkt aus static/ servieren — sofort sichtbar
// (Vorschau, Cover, Profilbild), ohne auf den nächsten Hugo-Build zu warten.
app.use('/images/*', serveStatic({ root: `${SITE_DIR}/static` }));

// --- Live-Site (gebaut nach public/) ---
app.use('/*', serveStatic({ root: `${SITE_DIR}/public` }));

serve({ fetch: app.fetch, port: PORT }, async (info) => {
  console.log(`CMS läuft auf :${info.port} — Site + API + /_preview`);
  // Plugin-Migrationen zuerst anwenden (run-once, getrackt) — die Boot-Hooks
  // unten brauchen die Tabellen schon. Ohne deklarierte Migrationen ein No-op.
  await runMigrations(manager, { databaseUrl: process.env.DATABASE_URL })
    .catch((e) => console.error('migrate:', e?.message || e));
  // Plugin-Boot-Hooks anstoßen (dialog z. B. spiegelt Library→Threads), nicht blockierend.
  manager.runBoot({}).catch((e) => console.error('plugin onBoot:', e?.message || e));
});
