import { Request, Response, NextFunction } from 'express';
// Native fetch available in Node 20+
import { getSystemConfig } from '../admin/system-config.service';

const PANSOU_UPSTREAM = 'http://pansou:80';

// Content types that need sub_filter path rewriting
const TEXT_TYPES = ['text/html', 'application/javascript', 'text/javascript'];

// Hop-by-hop headers that should not be forwarded
const HOP_HEADERS = new Set([
  'host', 'connection', 'content-length', 'transfer-encoding',
  'keep-alive', 'proxy-authenticate', 'proxy-authorization',
  'te', 'trailer', 'upgrade',
]);

/**
 * Apply sub_filter string replacements to HTML/JS content.
 * This matches what the nginx pansou.conf sub_filter does.
 */
function applySubFilter(text: string): string {
  return text
    // Replace HTML/JS path references: /api/ -> /pansou/api/
    .replace(/\/api\//g, '/pansou/api/')
    // baseURL rewrite (Vue SPA config)
    .replace(/baseURL:"\/api"/g, 'baseURL:"/pansou/api"')
    .replace(/baseURL:'\/api'/g, "baseURL:'/pansou/api'")
    // Static asset path rewrites
    .replace(/src="\/assets\//g, 'src="/pansou/assets/')
    .replace(/src='\/assets\//g, "src='/pansou/assets/")
    .replace(/href="\/assets\//g, 'href="/pansou/assets/')
    .replace(/href='\/assets\//g, "href='/pansou/assets/")
    // Favicon path rewrite
    .replace(/href="\/favicon\.ico/g, 'href="/pansou/favicon.ico')
    .replace(/href='\/favicon\.ico/g, "href='/pansou/favicon.ico");
}

/**
 * Express middleware that proxies /pansou/* requests to the PanSou web container.
 *
 * How it works:
 * 1. Strips the /pansou prefix from the request path
 * 2. Forwards the request to http://pansou:80/{path}
 * 3. For HTML/JS responses, applies sub_filter path rewriting
 *    so that /api/ becomes /pansou/api/ and /assets/ becomes /pansou/assets/
 * 4. For static assets (CSS, images, fonts), pipes through as-is
 *
 * Controlled by system config key 'pansou_web_enabled' (true/false).
 */
export async function pansouWebProxy(req: Request, res: Response, _next: NextFunction): Promise<void> {
  try {
    // Check if PanSou web is enabled
    const enabled = getSystemConfig('pansou_web_enabled');
    if (enabled !== 'true') {
      res.status(404).send('PanSou Web UI is disabled by administrator');
      return;
    }

    // Build upstream URL: strip /pansou prefix
    let targetPath = req.path;
    targetPath = targetPath.replace(/^\/pansou/, '') || '/';

    // Preserve query string
    const queryIndex = req.url.indexOf('?');
    const query = queryIndex >= 0 ? req.url.substring(queryIndex) : '';
    const upstreamUrl = `${PANSOU_UPSTREAM}${targetPath}${query}`;

    // Build forwarded headers (filter out hop-by-hop headers)
    const forwardHeaders: Record<string, string> = {};
    for (const [key, value] of Object.entries(req.headers)) {
      if (!HOP_HEADERS.has(key.toLowerCase()) && value !== undefined) {
        forwardHeaders[key] = Array.isArray(value) ? value.join(', ') : value;
      }
    }
    // Override Host header to target the upstream
    forwardHeaders['Host'] = 'pansou';
    // Remove Accept-Encoding so we get uncompressed content for text rewriting
    forwardHeaders['accept-encoding'] = '';

    // Forward the request
    const response = await fetch(upstreamUrl, {
      method: req.method as any,
      headers: forwardHeaders,
      body: ['GET', 'HEAD'].includes(req.method) ? undefined : JSON.stringify(req.body),
      redirect: 'manual',
      signal: AbortSignal.timeout(30000),
    });

    const contentType = response.headers.get('content-type') || '';

    // Set response status
    res.status(response.status);

    // Handle redirects - rewrite Location header to include /pansou prefix
    if (response.status >= 300 && response.status < 400) {
      const location = response.headers.get('location');
      if (location) {
        if (location.startsWith('/')) {
          res.setHeader('location', '/pansou' + location);
        } else {
          res.setHeader('location', location);
        }
      }
    }

    // For HTML/JS content, apply sub_filter string replacements
    if (TEXT_TYPES.some(t => contentType.includes(t))) {
      const text = await response.text();
      const modified = applySubFilter(text);
      res.setHeader('content-type', contentType);
      // Remove content-encoding since we decompressed
      res.setHeader('content-length', Buffer.byteLength(modified, 'utf-8').toString());
      res.send(modified);
      return;
    }

    // For other content (CSS, images, fonts, etc.), pipe through as-is
    const excludedHeaders = new Set([
      'content-encoding', 'content-length', 'transfer-encoding',
      'keep-alive', 'connection',
    ]);
    response.headers.forEach((value, key) => {
      if (!excludedHeaders.has(key.toLowerCase())) {
        res.setHeader(key, value);
      }
    });

    // Use buffer for reliability
    const buffer = await response.arrayBuffer().then(buf => Buffer.from(buf));
    res.end(buffer);
  } catch (err: any) {
    console.error(`[PanSou Web Proxy] Error proxying ${req.path}:`, err.message);
    if (!res.headersSent) {
      res.status(502).send(`PanSou Web proxy error: ${err.message}`);
    }
  }
}
