import { createServer } from "node:http";
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
import { extname, join, normalize } from "node:path";
import { fileURLToPath } from "node:url";
import { spawn } from "node:child_process";
import { dirname } from "node:path";

const repoRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
const port = Number(process.env.PORT || 8090);
const host = process.env.HOST || "127.0.0.1";
const pagePath = join(repoRoot, "content", "pages", "home.json");
const blockTemplatesPath = join(repoRoot, "content", "block-templates", "home.json");
const uploadRoot = join(repoRoot, "assets", "uploads");

const MIME = {
  ".html": "text/html; charset=utf-8",
  ".css": "text/css; charset=utf-8",
  ".js": "text/javascript; charset=utf-8",
  ".json": "application/json; charset=utf-8",
  ".svg": "image/svg+xml",
  ".png": "image/png",
  ".jpg": "image/jpeg",
  ".jpeg": "image/jpeg",
  ".gif": "image/gif",
  ".avif": "image/avif",
  ".webp": "image/webp",
  ".mp4": "video/mp4",
  ".webm": "video/webm",
  ".mov": "video/quicktime",
  ".m4v": "video/x-m4v",
  ".avi": "video/x-msvideo",
  ".mkv": "video/x-matroska",
  ".glb": "model/gltf-binary",
  ".gltf": "model/gltf+json",
  ".woff": "font/woff",
  ".woff2": "font/woff2",
};

function send(res, status, body, contentType = "text/plain; charset=utf-8") {
  res.writeHead(status, {
    "content-type": contentType,
    "cache-control": "no-store",
  });
  res.end(body);
}

function sendJson(res, status, payload) {
  send(res, status, `${JSON.stringify(payload, null, 2)}\n`, "application/json; charset=utf-8");
}

async function readBody(req) {
  const chunks = [];
  for await (const chunk of req) {
    chunks.push(chunk);
  }
  return Buffer.concat(chunks).toString("utf8");
}

function runNodeScript(scriptName) {
  return new Promise((resolve, reject) => {
    const child = spawn(process.execPath, [join(repoRoot, "tools", scriptName)], {
      cwd: repoRoot,
      stdio: ["ignore", "pipe", "pipe"],
    });

    let stdout = "";
    let stderr = "";
    child.stdout.on("data", (chunk) => {
      stdout += chunk.toString();
    });
    child.stderr.on("data", (chunk) => {
      stderr += chunk.toString();
    });
    child.on("error", reject);
    child.on("close", (code) => {
      if (code === 0) {
        resolve({ stdout, stderr });
      } else {
        reject(new Error(stderr || stdout || `${scriptName} exited with ${code}`));
      }
    });
  });
}

function isUploadPayload(payload) {
  return (
    payload &&
    typeof payload.fileName === "string" &&
    typeof payload.dataUrl === "string" &&
    (!payload.mimeType || typeof payload.mimeType === "string") &&
    (!payload.bucket || typeof payload.bucket === "string")
  );
}

function sanitizePathSegment(value) {
  const safe = value
    .normalize("NFKD")
    .replace(/[^\w.-]+/g, "-")
    .replace(/^-+|-+$/g, "")
    .toLowerCase();

  return safe || "media";
}

function sanitizeUploadBucket(value) {
  const segments = String(value || "media")
    .split(/[\\/]+/)
    .map((segment) => sanitizePathSegment(segment))
    .filter(Boolean)
    .slice(0, 4);

  return segments.length ? segments.join("/") : "media";
}

function extensionFromMimeType(mimeType) {
  const map = {
    "image/svg+xml": ".svg",
    "image/png": ".png",
    "image/jpeg": ".jpg",
    "image/gif": ".gif",
    "image/avif": ".avif",
    "image/webp": ".webp",
    "video/mp4": ".mp4",
    "video/webm": ".webm",
    "video/quicktime": ".mov",
    "model/gltf-binary": ".glb",
    "model/gltf+json": ".gltf",
    "font/woff": ".woff",
    "font/woff2": ".woff2",
  };

  return map[mimeType] || "";
}

function buildStoredFileName(fileName, mimeType) {
  const extension = (extname(fileName) || extensionFromMimeType(mimeType) || ".bin").toLowerCase();
  const base = fileName.slice(0, extension ? -extension.length : undefined);
  const safeBase =
    base
      .normalize("NFKD")
      .replace(/[^\w.-]+/g, "-")
      .replace(/^-+|-+$/g, "")
      .toLowerCase()
      .slice(0, 72) || "asset";

  return `${safeBase}-${Date.now().toString(36)}${extension}`;
}

async function saveUploadedAsset(payload) {
  if (!isUploadPayload(payload)) {
    throw new Error("Некорректный payload загрузки");
  }

  const match = /^data:([^;,]+)?(?:;[^,]*)?;base64,(.*)$/s.exec(payload.dataUrl);

  if (!match) {
    throw new Error("Файл должен прийти data-url с base64");
  }

  const mimeType = payload.mimeType || match[1] || "application/octet-stream";
  const bucket = sanitizeUploadBucket(payload.bucket || "media");
  const storedName = buildStoredFileName(payload.fileName, mimeType);
  const fileBuffer = Buffer.from(match[2], "base64");
  const directory = join(uploadRoot, ...bucket.split("/"));

  await mkdir(directory, { recursive: true });
  await writeFile(join(directory, storedName), fileBuffer);

  return {
    ok: true,
    url: `./assets/uploads/${bucket}/${storedName}`,
    fileName: storedName,
    originalFileName: payload.fileName,
    mimeType,
    bucket,
  };
}

function safePublicPath(urlPath) {
  const withoutQuery = decodeURIComponent(urlPath.split("?")[0]);
  const requestPath =
    withoutQuery === "/"
      ? "/index.html"
      : withoutQuery === "/admin" || withoutQuery === "/admin/"
        ? "/admin/index.html"
        : withoutQuery;
  const normalized = normalize(requestPath).replace(/^(\.\.[/\\])+/, "");
  const absolute = join(repoRoot, normalized);

  if (!absolute.startsWith(repoRoot)) {
    return null;
  }

  return absolute;
}

async function serveStatic(req, res) {
  const filePath = safePublicPath(new URL(req.url, `http://${host}:${port}`).pathname);
  if (!filePath) {
    send(res, 403, "Forbidden");
    return;
  }

  try {
    const fileStat = await stat(filePath);
    if (!fileStat.isFile()) {
      send(res, 404, "Not found");
      return;
    }

    const ext = extname(filePath);
    send(res, 200, await readFile(filePath), MIME[ext] || "application/octet-stream");
  } catch {
    send(res, 404, "Not found");
  }
}

const server = createServer(async (req, res) => {
  try {
    const url = new URL(req.url, `http://${host}:${port}`);

    if (req.method === "GET" && url.pathname === "/api/page/home") {
      send(res, 200, await readFile(pagePath, "utf8"), "application/json; charset=utf-8");
      return;
    }

    if (req.method === "GET" && url.pathname === "/api/block-templates/home") {
      send(res, 200, await readFile(blockTemplatesPath, "utf8"), "application/json; charset=utf-8");
      return;
    }

    if (req.method === "PUT" && url.pathname === "/api/page/home") {
      const body = await readBody(req);
      const parsed = JSON.parse(body);
      await writeFile(pagePath, `${JSON.stringify(parsed, null, 2)}\n`);
      sendJson(res, 200, { ok: true });
      return;
    }

    if (req.method === "POST" && url.pathname === "/api/render/home") {
      const result = await runNodeScript("render-home.mjs");
      sendJson(res, 200, { ok: true, ...result });
      return;
    }

    if (req.method === "POST" && url.pathname === "/api/upload/asset") {
      const body = await readBody(req);
      const result = await saveUploadedAsset(JSON.parse(body));
      sendJson(res, 200, result);
      return;
    }

    if (req.method === "POST" && url.pathname === "/api/extract/home") {
      const result = await runNodeScript("extract-home-blocks.mjs");
      sendJson(res, 200, { ok: true, ...result });
      return;
    }

    await serveStatic(req, res);
  } catch (error) {
    sendJson(res, 500, { ok: false, error: error.message });
  }
});

server.listen(port, host, () => {
  console.log(`NODE.DC admin: http://${host}:${port}/admin/`);
  console.log(`NODE.DC site:  http://${host}:${port}/`);
});
