import http from 'node:http';
import crypto from 'node:crypto';
import jwt from 'jsonwebtoken';
import { UserRole } from '@prisma/client';
import { env } from '../config/env';
import { logger } from '../utils/logger';

// HMAC secret — regenerated each server start (cookie sessions reset on restart)
const HMAC_SECRET = crypto.randomBytes(32);
const COOKIE_NAME = '_lmp_auth';
const COOKIE_MAX_AGE_S = 4 * 60 * 60; // 4 hours

// Extract Listmonk internal host/port from LISTMONK_URL (e.g. "http://listmonk-app:9000")
function parseListmonkTarget(): { hostname: string; port: number } {
  const url = new URL(env.LISTMONK_URL);
  return { hostname: url.hostname, port: parseInt(url.port || '9000', 10) };
}

// Basic auth header for Listmonk API user
function listmonkAuthHeader(): string {
  const creds = Buffer.from(`${env.LISTMONK_ADMIN_USER}:${env.LISTMONK_ADMIN_PASSWORD}`).toString('base64');
  return `Basic ${creds}`;
}

// --- Cookie signing/verification ---

interface CookiePayload {
  uid: string;
  exp: number; // seconds since epoch
}

function signCookie(payload: CookiePayload): string {
  const data = Buffer.from(JSON.stringify(payload)).toString('base64url');
  const sig = crypto.createHmac('sha256', HMAC_SECRET).update(data).digest('base64url');
  return `${data}.${sig}`;
}

function verifyCookie(cookie: string): CookiePayload | null {
  const dot = cookie.indexOf('.');
  if (dot === -1) return null;
  const data = cookie.slice(0, dot);
  const sig = cookie.slice(dot + 1);
  const expected = crypto.createHmac('sha256', HMAC_SECRET).update(data).digest('base64url');
  if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) return null;
  try {
    const payload = JSON.parse(Buffer.from(data, 'base64url').toString()) as CookiePayload;
    if (payload.exp < Date.now() / 1000) return null;
    return payload;
  } catch {
    return null;
  }
}

function parseCookies(header: string | undefined): Record<string, string> {
  const cookies: Record<string, string> = {};
  if (!header) return cookies;
  for (const pair of header.split(';')) {
    const eq = pair.indexOf('=');
    if (eq === -1) continue;
    const key = pair.slice(0, eq).trim();
    const val = pair.slice(eq + 1).trim();
    cookies[key] = val;
  }
  return cookies;
}

let server: http.Server | null = null;

export function startProxy(): http.Server {
  const target = parseListmonkTarget();

  server = http.createServer((req, res) => {
    const url = new URL(req.url || '/', `http://${req.headers.host}`);

    // --- /auth?token=<jwt> — exchange JWT for proxy cookie ---
    if (url.pathname === '/auth') {
      const token = url.searchParams.get('token');
      if (!token) {
        res.writeHead(400, { 'Content-Type': 'text/plain' });
        res.end('Missing token parameter');
        return;
      }

      try {
        const payload = jwt.verify(token, env.JWT_ACCESS_SECRET, { algorithms: ['HS256'] }) as {
          id: string;
          role: UserRole;
          roles?: UserRole[];
        };
        const payloadRoles = payload.roles || [payload.role];
        if (!payloadRoles.includes(UserRole.SUPER_ADMIN)) {
          res.writeHead(403, { 'Content-Type': 'text/plain' });
          res.end('Forbidden — SUPER_ADMIN role required');
          return;
        }

        const cookieVal = signCookie({
          uid: payload.id,
          exp: Math.floor(Date.now() / 1000) + COOKIE_MAX_AGE_S,
        });

        res.writeHead(302, {
          Location: '/admin/',
          'Set-Cookie': `${COOKIE_NAME}=${cookieVal}; HttpOnly; SameSite=Lax; Path=/; Max-Age=${COOKIE_MAX_AGE_S}`,
        });
        res.end();
      } catch {
        res.writeHead(401, { 'Content-Type': 'text/plain' });
        res.end('Invalid or expired token');
      }
      return;
    }

    // --- All other routes: verify cookie, proxy to Listmonk ---
    const cookies = parseCookies(req.headers.cookie);
    const cookieVal = cookies[COOKIE_NAME];
    if (!cookieVal || !verifyCookie(cookieVal)) {
      res.writeHead(403, { 'Content-Type': 'text/plain' });
      res.end('Forbidden — authenticate at /auth?token=<jwt> first');
      return;
    }

    // Build proxy request headers (copy originals, inject auth, remove cookie)
    const proxyHeaders: Record<string, string | string[] | undefined> = { ...req.headers };
    delete proxyHeaders.host;
    delete proxyHeaders.cookie;
    proxyHeaders.authorization = listmonkAuthHeader();

    const proxyReq = http.request(
      {
        hostname: target.hostname,
        port: target.port,
        path: req.url,
        method: req.method,
        headers: proxyHeaders,
      },
      (proxyRes) => {
        // Strip frame-busting headers from Listmonk responses
        const responseHeaders = { ...proxyRes.headers };
        delete responseHeaders['x-frame-options'];
        delete responseHeaders['content-security-policy'];

        res.writeHead(proxyRes.statusCode || 502, responseHeaders);
        proxyRes.pipe(res);
      },
    );

    proxyReq.on('error', (err) => {
      logger.error('Listmonk proxy error:', err);
      if (!res.headersSent) {
        res.writeHead(502, { 'Content-Type': 'text/plain' });
      }
      res.end('Listmonk is unreachable');
    });

    // Pipe request body for POST/PUT/PATCH
    req.pipe(proxyReq);
  });

  server.listen(env.LISTMONK_PROXY_PORT, () => {
    logger.info(`Listmonk proxy listening on port ${env.LISTMONK_PROXY_PORT}`);
  });

  return server;
}

export function stopProxy(): Promise<void> {
  return new Promise((resolve) => {
    if (server) {
      server.close(() => resolve());
    } else {
      resolve();
    }
  });
}
