import fs from "node:fs";
import http from "node:http";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { loadConfig } from "../config/config.js";
import { logDebug, logError } from "../logger.js";

const WEBCHAT_DEFAULT_PORT = 18788;

type WebChatServerState = {
  server: http.Server;
  port: number;
};

let state: WebChatServerState | null = null;

function resolveWebRoot() {
  const here = path.dirname(fileURLToPath(import.meta.url));

  const candidates = [
    // Bundled inside Clawdis.app: .../Contents/Resources/WebChat
    path.resolve(here, "../../../WebChat"),
    // When running from repo without bundling
    path.resolve(here, "../../WebChat"),
    // Fallback to source tree location
    path.resolve(here, "../../apps/macos/Sources/Clawdis/Resources/WebChat"),
  ];

  for (const candidate of candidates) {
    if (fs.existsSync(candidate)) return candidate;
  }

  throw new Error(`webchat assets not found; tried: ${candidates.join(", ")}`);
}

function notFound(res: http.ServerResponse) {
  res.statusCode = 404;
  res.end("Not Found");
}

function contentTypeForExt(ext: string) {
  switch (ext) {
    case ".html":
      return "text/html";
    case ".js":
      return "application/javascript";
    case ".css":
      return "text/css";
    case ".json":
    case ".map":
      return "application/json";
    case ".svg":
      return "image/svg+xml";
    case ".png":
      return "image/png";
    case ".ico":
      return "image/x-icon";
    default:
      return "application/octet-stream";
  }
}

export async function startWebChatServer(
  port = WEBCHAT_DEFAULT_PORT,
): Promise<WebChatServerState | null> {
  if (state) return state;

  const root = resolveWebRoot();

  const server = http.createServer(async (req, res) => {
    if (!req.url) return notFound(res);
    if (
      req.socket.remoteAddress &&
      !req.socket.remoteAddress.startsWith("127.")
    ) {
      res.statusCode = 403;
      res.end("loopback only");
      return;
    }

    const url = new URL(req.url, "http://127.0.0.1");

    if (url.pathname === "/webchat" || url.pathname.startsWith("/webchat/")) {
      let rel = url.pathname.replace(/^\/webchat\/?/, "");
      if (!rel || rel.endsWith("/")) rel = `${rel}index.html`;
      const filePath = path.join(root, rel);
      if (!filePath.startsWith(root) || !fs.existsSync(filePath)) {
        return notFound(res);
      }
      const data = fs.readFileSync(filePath);
      const ext = path.extname(filePath).toLowerCase();
      res.setHeader("Content-Type", contentTypeForExt(ext));
      res.end(data);
      return;
    }

    if (url.pathname === "/") {
      const filePath = path.join(root, "index.html");
      const data = fs.readFileSync(filePath);
      res.setHeader("Content-Type", "text/html");
      res.end(data);
      return;
    }

    const relPath = url.pathname.replace(/^\//, "");
    if (relPath) {
      const filePath = path.join(root, relPath);
      if (filePath.startsWith(root) && fs.existsSync(filePath)) {
        const data = fs.readFileSync(filePath);
        const ext = path.extname(filePath).toLowerCase();
        res.setHeader("Content-Type", contentTypeForExt(ext));
        res.end(data);
        return;
      }
    }

    notFound(res);
  });

  try {
    await new Promise<void>((resolve, reject) => {
      const onError = (err: Error) => reject(err);
      server.once("error", onError);
      server.listen(port, "127.0.0.1", () => {
        server.off("error", onError);
        resolve();
      });
    });
  } catch (err) {
    const code = (err as NodeJS.ErrnoException).code;
    const msg = code ? `${code}: ${String(err)}` : String(err);
    logError(
      `webchat server failed to bind 127.0.0.1:${port} (${msg}); continuing without webchat`,
    );
    return null;
  }

  state = { server, port };
  return state;
}

export async function stopWebChatServer() {
  if (!state) return;
  if (state.server) {
    await new Promise<void>((resolve) => state?.server.close(() => resolve()));
  }
  state = null;
}

// Legacy no-op: gateway readiness is now handled directly by clients.
export async function waitForWebChatGatewayReady() {
  return;
}

export function __forceWebChatSnapshotForTests() {
  // no-op: snapshots now come from the Gateway WS directly.
}

export async function __broadcastGatewayEventForTests() {
  // no-op
}

export async function ensureWebChatServerFromConfig() {
  const cfg = loadConfig();
  if (cfg.webchat?.enabled === false) return null;
  const port = cfg.webchat?.port ?? WEBCHAT_DEFAULT_PORT;
  try {
    return await startWebChatServer(port);
  } catch (err) {
    logDebug(`webchat server failed to start: ${String(err)}`);
    throw err;
  }
}
