import * as http from 'http';
import { parse } from 'url';
import { join, extname, normalize } from 'path';
import { createReadStream, statSync } from 'fs';
import next from 'next';
import { loadEnvConfig } from '@next/env';
import { childLogger } from './src/server/logger';
import { runNodeRequestLifecycle } from './src/server/logger/request';

loadEnvConfig(process.cwd());

const dev = process.env.NODE_ENV !== 'production';
const port = parseInt(process.env.PORT || '5000', 10);

const log = childLogger('server.bootstrap');

const app = next({ dev, hostname: '0.0.0.0', port });
const handle = app.getRequestHandler();

const MIME_TYPES: Record<string, string> = {
  '.png': 'image/png',
  '.jpg': 'image/jpeg',
  '.jpeg': 'image/jpeg',
  '.gif': 'image/gif',
  '.webp': 'image/webp',
  '.svg': 'image/svg+xml',
  '.ico': 'image/x-icon',
};

function serveUpload(req: http.IncomingMessage, res: http.ServerResponse): boolean {
  const rawUrl = req.url;
  if (!rawUrl || !rawUrl.startsWith('/uploads/')) return false;

  const urlPath = rawUrl.split('?')[0];

  let decoded: string;
  try {
    decoded = decodeURIComponent(urlPath);
  } catch {
    res.writeHead(400);
    res.end('Bad request');
    return true;
  }

  if (decoded.includes('..') || decoded.includes('\0')) {
    res.writeHead(400);
    res.end('Bad request');
    return true;
  }

  const normalized = normalize(decoded);
  if (!normalized.startsWith('/uploads/')) {
    res.writeHead(400);
    res.end('Bad request');
    return true;
  }

  const filePath = join(process.cwd(), 'public', normalized);

  let stat;
  try {
    stat = statSync(filePath);
  } catch {
    res.writeHead(404);
    res.end('Not found');
    return true;
  }
  if (!stat.isFile()) {
    res.writeHead(404);
    res.end('Not found');
    return true;
  }

  const ext = extname(filePath).toLowerCase();
  const contentType = MIME_TYPES[ext] || 'application/octet-stream';

  res.writeHead(200, {
    'Content-Type': contentType,
    'Cache-Control': 'public, max-age=31536000, immutable',
  });
  const stream = createReadStream(filePath);
  stream.on('error', () => {
    if (!res.headersSent) {
      res.writeHead(500);
    }
    res.end();
  });
  stream.pipe(res);
  return true;
}

app.prepare().then(async () => {
  const { engineRegistry } = await import('./src/server/engine/index');

  const server = http.createServer((req, res) => {
    // Wrap only the static-uploads branch in the lifecycle middleware so the
    // request gets a `reqId` and start/end log lines. Next.js route handlers
    // are wrapped separately by `withErrorHandler`, so we let the Next handler
    // through untouched (otherwise every route would be double-logged).
    if (req.url && req.url.startsWith('/uploads/')) {
      runNodeRequestLifecycle(req, res, () => {
        if (serveUpload(req, res)) return;
        const parsedUrl = parse(req.url!, true);
        handle(req, res, parsedUrl);
      });
      return;
    }
    const parsedUrl = parse(req.url!, true);
    handle(req, res, parsedUrl);
  });

  engineRegistry.attach(server);

  server.listen(port, '0.0.0.0', () => {
    log.info(
      { port, env: dev ? 'dev' : 'production' },
      `RestroAgent ready on http://0.0.0.0:${port}`
    );
  });
}).catch((err) => {
  log.fatal({ err }, 'Failed to start server');
  process.exit(1);
});
