import { createReadStream, existsSync, statSync } from 'node:fs';
import { extname, isAbsolute, join, normalize } from 'node:path';
import { createServer } from 'node:http';
import { connect as connectNet } from 'node:net';
import { Readable } from 'node:stream';
import { connect as connectTls } from 'node:tls';

const port = Number(process.env.PORT ?? 5173);
const distDirName = process.env.APP_DIST_DIR ?? 'app-dist';
const rootDir = normalize(isAbsolute(distDirName) ? distDirName : join(process.cwd(), distDirName));
const workServerUrl = new URL(process.env.WORK_SERVER_URL ?? 'http://127.0.0.1:3100');
const proxyPrefixes = ['/api', '/.codex_chat', '/public/.codex_chat', '/ws/chat'];

const mimeTypes = {
  '.css': 'text/css; charset=utf-8',
  '.html': 'text/html; charset=utf-8',
  '.ico': 'image/x-icon',
  '.js': 'text/javascript; charset=utf-8',
  '.json': 'application/json; charset=utf-8',
  '.mjs': 'text/javascript; charset=utf-8',
  '.png': 'image/png',
  '.svg': 'image/svg+xml',
  '.txt': 'text/plain; charset=utf-8',
  '.webmanifest': 'application/manifest+json; charset=utf-8',
  '.woff2': 'font/woff2',
};

function canListenOnPort(candidatePort, host = '0.0.0.0') {
  return new Promise((resolve, reject) => {
    const probeServer = createServer();

    probeServer.once('error', (error) => {
      probeServer.close(() => {
        if (error && typeof error === 'object' && 'code' in error && error.code === 'EADDRINUSE') {
          resolve(false);
          return;
        }

        reject(error);
      });
    });

    probeServer.once('listening', () => {
      probeServer.close((closeError) => {
        if (closeError) {
          reject(closeError);
          return;
        }

        resolve(true);
      });
    });

    probeServer.listen(candidatePort, host);
  });
}

async function findAvailablePort(initialPort, host = '0.0.0.0', maxAttempts = 20) {
  for (let offset = 0; offset < maxAttempts; offset += 1) {
    const candidatePort = initialPort + offset;
    const available = await canListenOnPort(candidatePort, host);

    if (available) {
      return candidatePort;
    }

    if (offset === 0) {
      console.warn(`Port ${initialPort} is in use, trying another one...`);
    }
  }

  throw new Error(`No available port found from ${initialPort} to ${initialPort + maxAttempts - 1}.`);
}

function resolveCacheControl(resolvedPath, extension) {
  const normalizedPath = resolvedPath.replace(/\\/g, '/');

  if (extension === '.html') {
    return 'no-cache';
  }

  if (normalizedPath.endsWith('/sw.js') || extension === '.webmanifest') {
    return 'no-store, no-cache, max-age=0, must-revalidate';
  }

  return 'public, max-age=31536000, immutable';
}

function looksLikeStaticAsset(requestedPath) {
  const normalizedPath = requestedPath.split('?')[0] ?? requestedPath;
  const extension = extname(normalizedPath);

  return extension.length > 0;
}

function resolvePath(urlPath) {
  const decodedPath = decodeURIComponent(urlPath.split('?')[0] || '/');
  const requestedPath = decodedPath === '/' ? '/index.html' : decodedPath;
  const absolutePath = normalize(join(rootDir, requestedPath));

  if (!absolutePath.startsWith(rootDir)) {
    return null;
  }

  if (existsSync(absolutePath) && statSync(absolutePath).isFile()) {
    return absolutePath;
  }

  if (looksLikeStaticAsset(requestedPath)) {
    return null;
  }

  return join(rootDir, 'index.html');
}

function shouldProxyRequest(urlPath = '/') {
  const normalizedPath = urlPath.split('?')[0] ?? urlPath;
  return proxyPrefixes.some((prefix) => normalizedPath === prefix || normalizedPath.startsWith(`${prefix}/`));
}

function readRequestBody(request) {
  return new Promise((resolve, reject) => {
    const chunks = [];

    request.on('data', (chunk) => {
      chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
    });
    request.on('end', () => {
      resolve(chunks.length > 0 ? Buffer.concat(chunks) : null);
    });
    request.on('error', reject);
  });
}

async function proxyRequest(request, response) {
  const targetUrl = new URL(request.url ?? '/', workServerUrl);
  const headers = new Headers();

  Object.entries(request.headers).forEach(([key, value]) => {
    if (value == null || key.toLowerCase() === 'host' || key.toLowerCase() === 'connection') {
      return;
    }

    if (Array.isArray(value)) {
      value.forEach((item) => headers.append(key, item));
      return;
    }

    headers.set(key, value);
  });

  const method = request.method ?? 'GET';
  const body = method === 'GET' || method === 'HEAD' ? undefined : await readRequestBody(request);

  try {
    const upstreamResponse = await fetch(targetUrl, {
      method,
      headers,
      body,
    });

    response.writeHead(
      upstreamResponse.status,
      Object.fromEntries(upstreamResponse.headers.entries()),
    );

    if (!upstreamResponse.body) {
      response.end();
      return;
    }

    Readable.fromWeb(upstreamResponse.body).pipe(response);
  } catch (error) {
    response.writeHead(502, { 'Content-Type': 'application/json; charset=utf-8' });
    response.end(
      JSON.stringify({
        message: error instanceof Error ? error.message : 'Failed to proxy request to work server.',
      }),
    );
  }
}

const server = createServer(async (request, response) => {
  if (shouldProxyRequest(request.url ?? '/')) {
    await proxyRequest(request, response);
    return;
  }

  const resolvedPath = resolvePath(request.url ?? '/');

  if (!resolvedPath || !existsSync(resolvedPath)) {
    response.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
    response.end('Not Found');
    return;
  }

  const extension = extname(resolvedPath);
  const contentType = mimeTypes[extension] ?? 'application/octet-stream';

  response.writeHead(200, {
    'Content-Type': contentType,
    'Cache-Control': resolveCacheControl(resolvedPath, extension),
  });

  createReadStream(resolvedPath).pipe(response);
});

server.on('upgrade', (request, socket, head) => {
  if (!shouldProxyRequest(request.url ?? '/')) {
    socket.destroy();
    return;
  }

  const upstreamPort = Number(
    workServerUrl.port || (workServerUrl.protocol === 'https:' ? '443' : '80'),
  );
  const upstreamSocket =
    workServerUrl.protocol === 'https:'
      ? connectTls(upstreamPort, workServerUrl.hostname, { servername: workServerUrl.hostname })
      : connectNet(upstreamPort, workServerUrl.hostname);

  upstreamSocket.on('connect', () => {
    const headerLines = Object.entries(request.headers)
      .flatMap(([key, value]) => {
        if (value == null || key.toLowerCase() === 'host') {
          return [];
        }

        return Array.isArray(value)
          ? value.map((item) => `${key}: ${item}\r\n`)
          : [`${key}: ${value}\r\n`];
      })
      .join('');

    const requestLine = `${request.method ?? 'GET'} ${request.url ?? '/'} HTTP/${request.httpVersion}\r\n`;
    upstreamSocket.write(`${requestLine}host: ${workServerUrl.host}\r\n${headerLines}\r\n`);

    if (head?.length) {
      upstreamSocket.write(head);
    }
  });

  upstreamSocket.on('data', (chunk) => {
    socket.write(chunk);
  });

  upstreamSocket.on('end', () => {
    socket.end();
  });

  upstreamSocket.on('error', () => {
    socket.destroy();
  });

  socket.on('data', (chunk) => {
    upstreamSocket.write(chunk);
  });

  socket.on('end', () => {
    upstreamSocket.end();
  });

  socket.on('error', () => {
    upstreamSocket.destroy();
  });
});

const host = '0.0.0.0';
const resolvedPort = await findAvailablePort(port, host);

server.listen(resolvedPort, host, () => {
  console.log(`${distDirName} server listening on http://${host}:${resolvedPort}`);
});
