import { createServer, request } from "node:http";
import { appendFile, mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { extname, join, normalize, resolve, sep } from "node:path";

const contentTypes = new Map([
  [".css", "text/css; charset=utf-8"], [".html", "text/html; charset=utf-8"],
  [".js", "text/javascript; charset=utf-8"], [".json", "application/json; charset=utf-8"],
  [".png", "image/png"], [".svg", "image/svg+xml"], [".webmanifest", "application/manifest+json"]
]);
const LOG_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
const DEFAULT_LOG_MAX_BYTES = 1024 * 1024;
const DEFAULT_LOG_MAX_CLEANUP = 32;

export function temporaryDirectory() {
  return mkdtemp(join(tmpdir(), "code-remote-manager-host-"));
}

export function productionPort(env = process.env) {
  const value = env.CODE_REMOTE_MANAGER_PWA_PORT ?? "43111";
  const port = Number(value);
  if (!Number.isInteger(port) || port <= 0 || port > 65535) {
    throw new Error("Invalid production host port.");
  }
  return port;
}

export async function appendRetainedLog(logsDir, name, chunk, options = {}) {
  if (!/^[a-z][a-z0-9-]*$/.test(name)) throw new Error("Invalid log name.");
  const now = options.now instanceof Date && Number.isFinite(options.now.getTime()) ? options.now : new Date();
  const maxBytes = positiveInteger(options.maxBytes, DEFAULT_LOG_MAX_BYTES);
  const maxCleanup = positiveInteger(options.maxCleanup, DEFAULT_LOG_MAX_CLEANUP);
  const retentionMs = positiveInteger(options.retentionMs, LOG_RETENTION_MS);
  await mkdir(logsDir, { recursive: true });
  await cleanupExpiredLogs(logsDir, now.getTime() - retentionMs, maxCleanup);

  const path = join(logsDir, `${name}-${now.toISOString().slice(0, 10)}.log`);
  const safe = redactHostLog(chunk);
  await appendFile(path, safe);
  const content = await readFile(path);
  if (content.byteLength > maxBytes) await writeFile(path, content.subarray(content.byteLength - maxBytes));
  return path;
}

async function cleanupExpiredLogs(logsDir, cutoff, maxCleanup) {
  const entries = await readdir(logsDir, { withFileTypes: true }).catch(() => []);
  const expired = [];
  for (const entry of entries) {
    if (!entry.isFile() || !entry.name.endsWith(".log")) continue;
    const path = join(logsDir, entry.name);
    const info = await stat(path).catch(() => undefined);
    if (info && info.mtimeMs < cutoff) expired.push({ path, mtimeMs: info.mtimeMs });
  }
  expired.sort((left, right) => left.mtimeMs - right.mtimeMs || left.path.localeCompare(right.path));
  await Promise.all(expired.slice(0, maxCleanup).map((item) => rm(item.path, { force: true }).catch(() => {})));
}

function redactHostLog(value) {
  return String(value?.message ?? value ?? "")
    .replace(/\b(Set-Cookie|Cookie)\s*:\s*[^\r\n]*/gi, "$1: [redacted]")
    .replace(/\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]+/gi, "$1 [redacted]")
    .replace(/\b(api[_-]?key|password|token|credential)\s*[:=]\s*[^\s\r\n]+/gi, "$1=[redacted]")
    .replace(/(["'])(?:[A-Za-z]:[\\/]|\\{2,})[^"'\r\n]+\1/g, "$1[local path redacted]$1")
    .replace(/\b[A-Za-z]:[\\/][^\r\n"',;|<>]*/g, "[local path redacted]")
    .replace(/\b(?:auth|credentials?|cookies?)\.(?:json|sqlite|db)\b/gi, "[credential-file redacted]");
}

function positiveInteger(value, fallback) {
  return Number.isSafeInteger(value) && value > 0 ? value : fallback;
}

export function createProductionServer({ publicDir, backendPort = 43110 }) {
  const publicRoot = resolve(publicDir);
  return createServer(async (req, res) => {
    try {
      const url = new URL(req.url ?? "/", "http://127.0.0.1");
      if (url.pathname === "/api" || url.pathname.startsWith("/api/")) {
        proxyRequest(req, res, backendPort, `${url.pathname.slice(4) || "/"}${url.search}`);
        return;
      }
      if (!['GET', 'HEAD'].includes(req.method ?? 'GET')) {
        res.writeHead(405, { "content-type": "text/plain; charset=utf-8" });
        res.end("Method not allowed");
        return;
      }
      const requested = normalize(decodeURIComponent(url.pathname)).replace(/^([/\\])+/, "");
      let filePath = resolve(publicRoot, requested || "index.html");
      if (filePath !== publicRoot && !filePath.startsWith(`${publicRoot}${sep}`)) {
        res.writeHead(404); res.end(); return;
      }
      try {
        if ((await stat(filePath)).isDirectory()) filePath = join(filePath, "index.html");
      } catch {
        filePath = join(publicRoot, "index.html");
      }
      const body = await readFile(filePath);
      res.writeHead(200, {
        "content-type": contentTypes.get(extname(filePath).toLowerCase()) ?? "application/octet-stream",
        "cache-control": extname(filePath) === ".html" ? "no-cache" : "public, max-age=86400"
      });
      res.end(req.method === "HEAD" ? undefined : body);
    } catch {
      res.writeHead(500, { "content-type": "text/plain; charset=utf-8" });
      res.end("Code Remote Manager host error");
    }
  });
}

function proxyRequest(clientRequest, clientResponse, backendPort, path) {
  const headers = { ...clientRequest.headers, host: `127.0.0.1:${backendPort}` };
  const upstream = request({ hostname: "127.0.0.1", port: backendPort, method: clientRequest.method, path, headers }, (response) => {
    clientResponse.writeHead(response.statusCode ?? 502, response.headers);
    response.pipe(clientResponse);
  });
  upstream.on("error", () => {
    if (!clientResponse.headersSent) clientResponse.writeHead(502, { "content-type": "application/json" });
    clientResponse.end('{"error":"host_unavailable"}');
  });
  clientRequest.on("aborted", () => upstream.destroy());
  clientRequest.pipe(upstream);
}
