// ─────────────────────────────────────────────────────────────
// server.js — entry point.
//
// Binds to 127.0.0.1 only. Nginx reverse-proxies to this port.
// Never expose Node directly to the public internet.
// ─────────────────────────────────────────────────────────────
import 'dotenv/config';
import express from 'express';
import cookieParser from 'cookie-parser';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

import authRouter from './src/auth.js';
import { requireAuth } from './src/middleware.js';
import { q } from './src/db.js'; // also side-effect: opens DB + runs schema

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const app = express();

// ─── Trust Nginx as the single upstream proxy ────────────────
// This makes req.ip reflect X-Forwarded-For (the real client IP)
// instead of 127.0.0.1. Only "loopback" is trusted, so spoofed
// XFF headers from the public internet are ignored.
app.set('trust proxy', 'loopback');

// ─── Body parsers ────────────────────────────────────────────
app.use(express.json({ limit: '4kb' }));
app.use(cookieParser());

// ─── Security headers on every response ──────────────────────
app.use((req, res, next) => {
  res.set({
    'X-Content-Type-Options': 'nosniff',
    'X-Frame-Options': 'DENY',
    'Referrer-Policy': 'strict-origin-when-cross-origin',
    'Permissions-Policy': 'camera=(), microphone=(), geolocation=()',
    // Tight CSP — only our own origin, and inline <style> blocks are allowed
    // because the design system ships styles inline inside the artifacts.
    'Content-Security-Policy': [
      "default-src 'self'",
      "script-src 'self'",
      "style-src 'self' 'unsafe-inline'",
      "img-src 'self' data:",
      "font-src 'self'",
      "connect-src 'self'",
      "frame-ancestors 'none'",
      "base-uri 'self'",
      "form-action 'self'",
    ].join('; '),
  });
  next();
});

// ─── Tiny request log ────────────────────────────────────────
app.use((req, res, next) => {
  const started = Date.now();
  res.on('finish', () => {
    const ms = Date.now() - started;
    const line = `${new Date().toISOString()} ${req.ip} ${req.method} ${req.path} → ${res.statusCode} (${ms}ms)`;
    console.log(line);
  });
  next();
});

// ─── Auth endpoints (public) ─────────────────────────────────
app.use('/auth', authRouter);

// ─── Bifrost join tracking (gated) ───────────────────────────
// Records every click of the final CTA button as its own row so the
// full history per user is preserved (who, when, which session).
app.post('/api/bifrost-join', requireAuth, (req, res) => {
  const { email, id: sessionId } = req.session;
  const clickedAt = Date.now();
  q.recordJoin.run(email, clickedAt, sessionId);
  res.json({ clicked_at: clickedAt });
});

// ─── Root dispatch ───────────────────────────────────────────
// GET /           → always the entrance shell. If authed, entrance.js routes
//                   the user to the welcome step client-side.
// GET /timeline   → gated timeline page (protected/index.html).
app.get('/', (req, res) => {
  return res.sendFile(path.join(__dirname, 'public', 'entrance.html'));
});

app.get('/timeline', requireAuth, (req, res) => {
  return res.sendFile(path.join(__dirname, 'protected', 'index.html'));
});

// ─── Public static assets (entrance.js, etc.) ────────────────
// Fallthrough so Express can still try the routes below if nothing matches.
app.use(
  express.static(path.join(__dirname, 'public'), {
    index: false,        // don't auto-serve entrance.html at /
    extensions: ['html'],
    fallthrough: true,
    maxAge: 0,
  })
);

// ─── GATED — everything in protected/ needs a session cookie ─
// Handles the authed home page assets:
//   /timeline.js, /vendor/*, /fenja/colors_and_type.css, /archive.html, etc.
app.use(
  requireAuth,
  express.static(path.join(__dirname, 'protected'), {
    index: false,        // we dispatch / ourselves above
    extensions: ['html'],
    maxAge: 0,
  })
);

// ─── 404 fallback ────────────────────────────────────────────
app.use((req, res) => {
  if (req.accepts('html')) {
    return res.status(404).send('<h1>404 — not found</h1>');
  }
  res.status(404).end();
});

// ─── Error handler ───────────────────────────────────────────
app.use((err, req, res, _next) => {
  console.error('[error]', err);
  res.status(500).json({ error: 'internal' });
});

// ─── Start ───────────────────────────────────────────────────
const port = Number(process.env.PORT || 3000);
const origin = process.env.PUBLIC_ORIGIN || `http://127.0.0.1:${port}`;

app.listen(port, '127.0.0.1', () => {
  console.log(`[bifrost] listening on 127.0.0.1:${port}`);
  console.log(`[bifrost] public origin: ${origin}`);
});

// ─── Graceful shutdown ───────────────────────────────────────
for (const sig of ['SIGINT', 'SIGTERM']) {
  process.on(sig, () => {
    console.log(`[bifrost] ${sig} received, exiting.`);
    process.exit(0);
  });
}
