import http from "node:http";
import { existsSync, readFileSync, statSync } from "node:fs";
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";

const scriptDirectory = path.dirname(fileURLToPath(import.meta.url));
const siteRoot = path.resolve(process.env.MOBAI_SITE_ROOT || path.join(scriptDirectory, "site"));
const clientRoot = path.join(siteRoot, "client");
const serverEntry = path.join(siteRoot, "server", "index.js");
const host = process.env.MOBAI_SITE_HOST || "127.0.0.1";
const port = Number(process.env.MOBAI_SITE_PORT || 3000);

if (!existsSync(serverEntry)) {
  throw new Error(`墨白服务入口不存在：${serverEntry}`);
}

const { default: worker } = await import(pathToFileURL(serverEntry).href);

const MIME_TYPES = new Map([
  [".css", "text/css; charset=utf-8"],
  [".gif", "image/gif"],
  [".html", "text/html; charset=utf-8"],
  [".ico", "image/x-icon"],
  [".jpeg", "image/jpeg"],
  [".jpg", "image/jpeg"],
  [".js", "text/javascript; charset=utf-8"],
  [".json", "application/json; charset=utf-8"],
  [".map", "application/json; charset=utf-8"],
  [".png", "image/png"],
  [".svg", "image/svg+xml; charset=utf-8"],
  [".txt", "text/plain; charset=utf-8"],
  [".webp", "image/webp"],
  [".woff", "font/woff"],
  [".woff2", "font/woff2"],
]);

function safeClientPath(url) {
  let pathname;
  try {
    pathname = decodeURIComponent(new URL(url).pathname);
  } catch {
    return null;
  }
  const relative = pathname.replace(/^\/+/, "");
  const resolved = path.resolve(clientRoot, relative);
  if (resolved !== clientRoot && !resolved.startsWith(`${clientRoot}${path.sep}`)) return null;
  return resolved;
}

function staticResponse(request) {
  const resolved = safeClientPath(request.url);
  if (!resolved || !existsSync(resolved) || !statSync(resolved).isFile()) return null;
  const headers = new Headers({
    "Content-Type": MIME_TYPES.get(path.extname(resolved).toLowerCase()) || "application/octet-stream",
    "Cache-Control": resolved.includes(`${path.sep}assets${path.sep}`)
      ? "public, max-age=31536000, immutable"
      : "no-cache",
  });
  if (request.method === "HEAD") return new Response(null, { status: 200, headers });
  return new Response(readFileSync(resolved), { status: 200, headers });
}

const env = {
  ASSETS: {
    async fetch(request) {
      return staticResponse(request) || new Response("Not found", { status: 404 });
    },
  },
  IMAGES: {
    input(stream) {
      return {
        transform() {
          return {
            output() {
              return { response: async () => new Response(stream) };
            },
          };
        },
      };
    },
  },
};

const executionContext = {
  waitUntil() {},
  passThroughOnException() {},
};

function toRequest(incoming) {
  const origin = `http://${incoming.headers.host || `${host}:${port}`}`;
  const init = {
    method: incoming.method,
    headers: incoming.headers,
  };
  if (incoming.method !== "GET" && incoming.method !== "HEAD") {
    init.body = incoming;
    init.duplex = "half";
  }
  return new Request(new URL(incoming.url || "/", origin), init);
}

async function sendResponse(response, outgoing) {
  outgoing.statusCode = response.status;
  for (const [name, value] of response.headers) outgoing.setHeader(name, value);
  if (response.body === null) {
    outgoing.end();
    return;
  }
  const body = Buffer.from(await response.arrayBuffer());
  outgoing.end(body);
}

const server = http.createServer(async (incoming, outgoing) => {
  try {
    const request = toRequest(incoming);
    const directAsset = staticResponse(request);
    const response = directAsset || await worker.fetch(request, env, executionContext);
    await sendResponse(response, outgoing);
  } catch (error) {
    outgoing.writeHead(500, { "Content-Type": "text/plain; charset=utf-8" });
    outgoing.end(error instanceof Error ? error.message : "墨白本地服务异常");
  }
});

server.listen(port, host, () => {
  process.stderr.write(`[mobai-site] listening on http://${host}:${port}\n`);
});

function shutdown() {
  server.close(() => process.exit(0));
  setTimeout(() => process.exit(0), 1500).unref();
}

process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
