import { readFile, stat } from "node:fs/promises";
import { createServer } from "node:http";
import { extname, join, normalize, resolve } from "node:path";

const distDir = resolve(process.env.FRONTEND_DIST_DIR ?? "/app/dist");
const host = process.env.HOST ?? "0.0.0.0";
const port = Number(process.env.PORT ?? 5173);
const backendTarget = new URL(
  process.env.API_PROXY_TARGET || "http://localhost:3000",
);

const contentTypes = {
  ".css": "text/css; charset=utf-8",
  ".html": "text/html; charset=utf-8",
  ".ico": "image/x-icon",
  ".js": "application/javascript; charset=utf-8",
  ".json": "application/json; charset=utf-8",
  ".map": "application/json; charset=utf-8",
  ".mjs": "application/javascript; charset=utf-8",
  ".png": "image/png",
  ".svg": "image/svg+xml",
  ".txt": "text/plain; charset=utf-8",
  ".webp": "image/webp",
  ".woff": "font/woff",
  ".woff2": "font/woff2",
};

function getContentType(filePath) {
  return (
    contentTypes[extname(filePath).toLowerCase()] ?? "application/octet-stream"
  );
}

function sendJson(res, statusCode, body) {
  res.writeHead(statusCode, {
    "Content-Type": "application/json; charset=utf-8",
    "Cache-Control": "no-store",
  });
  res.end(JSON.stringify(body));
}

function toSafePath(pathname) {
  const decoded = decodeURIComponent(pathname);
  const relative = decoded.replace(/^\/+/, "");
  const safe = normalize(relative).replace(/^(\.\.(?:[\\/]|$))+/, "");
  return join(distDir, safe);
}

async function tryReadFile(filePath) {
  try {
    return await readFile(filePath);
  } catch {
    return null;
  }
}

async function proxyToBackend(req, res, pathname, search) {
  const target = new URL(pathname + search, backendTarget);
  const headers = new Headers();

  for (const [key, value] of Object.entries(req.headers)) {
    if (!value) continue;
    if (key === "host" || key === "content-length" || key === "connection") {
      continue;
    }
    headers.set(key, Array.isArray(value) ? value.join(", ") : value);
  }

  const hasBody = !["GET", "HEAD"].includes(req.method ?? "GET");
  const response = await fetch(target, {
    method: req.method,
    headers,
    body: hasBody ? req : undefined,
    duplex: hasBody ? "half" : undefined,
  });

  const responseHeaders = new Headers(response.headers);
  responseHeaders.delete("content-length");
  responseHeaders.delete("transfer-encoding");
  responseHeaders.delete("connection");

  res.writeHead(response.status, Object.fromEntries(responseHeaders.entries()));

  if (req.method === "HEAD") {
    res.end();
    return;
  }

  const arrayBuffer = await response.arrayBuffer();
  res.end(Buffer.from(arrayBuffer));
}

async function serveStatic(req, res, pathname) {
  const indexPath = join(distDir, "index.html");
  const filePath = toSafePath(pathname);

  let resolvedPath = filePath;
  try {
    const fileStat = await stat(resolvedPath);
    if (fileStat.isDirectory()) {
      resolvedPath = join(resolvedPath, "index.html");
    }
  } catch {
    resolvedPath = indexPath;
  }

  let body = await tryReadFile(resolvedPath);
  if (!body) {
    body = await tryReadFile(indexPath);
    resolvedPath = indexPath;
  }

  if (!body) {
    sendJson(res, 500, { error: "dist_not_found" });
    return;
  }

  res.writeHead(200, {
    "Content-Type": getContentType(resolvedPath),
    "Cache-Control": resolvedPath.endsWith("index.html")
      ? "no-cache"
      : "public, max-age=31536000, immutable",
  });

  if (req.method === "HEAD") {
    res.end();
    return;
  }

  res.end(body);
}

createServer(async (req, res) => {
  try {
    const url = new URL(
      req.url ?? "/",
      `http://${req.headers.host ?? "localhost"}`,
    );
    const { pathname, search } = url;

    if (pathname === "/api" || pathname.startsWith("/api/")) {
      await proxyToBackend(req, res, pathname, search);
      return;
    }

    await serveStatic(req, res, pathname === "/" ? "/index.html" : pathname);
  } catch (error) {
    sendJson(res, 500, {
      error: "internal_server_error",
      message: error instanceof Error ? error.message : String(error),
    });
  }
}).listen(port, host, () => {
  console.log(`Frontend server listening on http://${host}:${port}`);
});
