const crypto = require("crypto");
const fs = require("fs");
const http = require("http");
const path = require("path");
const { URL } = require("url");

const ROOT = __dirname;
const DATA_FILE = path.join(ROOT, "data", "db.json");
const PORT = Number(process.env.PORT || 3000);
const ADMIN_USER = process.env.ADMIN_USER || "admin";
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || "admin123";
const sessions = new Set();

const MIME_TYPES = {
  ".css": "text/css; charset=utf-8",
  ".js": "text/javascript; charset=utf-8",
  ".html": "text/html; charset=utf-8",
  ".json": "application/json; charset=utf-8",
  ".png": "image/png",
  ".jpg": "image/jpeg",
  ".jpeg": "image/jpeg",
  ".webp": "image/webp",
  ".svg": "image/svg+xml",
  ".ico": "image/x-icon",
  ".wasm": "application/wasm",
  ".otf": "font/otf",
  ".ttf": "font/ttf",
  ".woff": "font/woff",
  ".woff2": "font/woff2",
};

function readDb() {
  const db = JSON.parse(fs.readFileSync(DATA_FILE, "utf8"));
  db.games ||= [];
  db.posts ||= [];
  return db;
}

function writeDb(db) {
  fs.writeFileSync(DATA_FILE, `${JSON.stringify(db, null, 2)}\n`, "utf8");
}

function activeGames() {
  return readDb()
    .games.filter((game) => game.active)
    .sort((a, b) => Number(a.sort) - Number(b.sort));
}

function findGame(slug) {
  return activeGames().find((game) => game.slug === slug);
}

function activePosts() {
  return readDb()
    .posts.filter((post) => post.active)
    .sort((a, b) => new Date(b.updatedAt || b.createdAt) - new Date(a.updatedAt || a.createdAt));
}

function findPost(slug) {
  return activePosts().find((post) => post.slug === slug);
}

function escapeHtml(value = "") {
  return String(value)
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#039;");
}

function sanitizePostHtml(value = "") {
  return String(value)
    .replace(/<(script|style|iframe|object|embed|form|button)\b[^>]*>[\s\S]*?<\/\1>/gi, "")
    .replace(/<(script|style|iframe|object|embed|form|input|button|meta|link|base)\b[^>]*\/?>/gi, "")
    .replace(/\son\w+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, "")
    .replace(/\s(href|src)\s*=\s*(["'])\s*javascript:[\s\S]*?\2/gi, "")
    .replace(/\s(href|src)\s*=\s*javascript:[^\s>]+/gi, "");
}

function stripHtml(value = "") {
  return String(value).replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
}

function slugify(value) {
  return value
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")
    .toLowerCase()
    .replace(/đ/g, "d")
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-+|-+$/g, "");
}

function json(res, status, data) {
  send(res, status, JSON.stringify(data), "application/json; charset=utf-8");
}

function send(res, status, body, contentType = "text/html; charset=utf-8", headers = {}) {
  res.writeHead(status, {
    "Content-Type": contentType,
    "X-Content-Type-Options": "nosniff",
    ...headers,
  });
  res.end(body);
}

function redirect(res, location) {
  res.writeHead(302, { Location: location });
  res.end();
}

function parseCookies(req) {
  return Object.fromEntries(
    (req.headers.cookie || "")
      .split(";")
      .map((item) => item.trim().split("="))
      .filter(([key]) => key)
  );
}

function isAuthed(req) {
  return sessions.has(parseCookies(req).admin_session);
}

function readBody(req) {
  return new Promise((resolve, reject) => {
    let body = "";
    req.on("data", (chunk) => {
      body += chunk;
      if (body.length > 1_000_000) {
        req.destroy();
        reject(new Error("Body too large"));
      }
    });
    req.on("end", () => resolve(body));
    req.on("error", reject);
  });
}

function readBuffer(req) {
  return new Promise((resolve, reject) => {
    const chunks = [];
    let size = 0;
    req.on("data", (chunk) => {
      size += chunk.length;
      if (size > 300_000_000) {
        req.destroy();
        reject(new Error("Upload too large"));
        return;
      }
      chunks.push(chunk);
    });
    req.on("end", () => resolve(Buffer.concat(chunks)));
    req.on("error", reject);
  });
}

function parseMultipart(buffer, contentType) {
  const boundaryMatch = /boundary=(?:"([^"]+)"|([^;]+))/i.exec(contentType || "");
  if (!boundaryMatch) throw new Error("Missing multipart boundary");

  const boundary = Buffer.from(`--${boundaryMatch[1] || boundaryMatch[2]}`);
  const parts = [];
  let start = buffer.indexOf(boundary);

  while (start !== -1) {
    start += boundary.length;
    if (buffer[start] === 45 && buffer[start + 1] === 45) break;
    if (buffer[start] === 13 && buffer[start + 1] === 10) start += 2;

    const headerEnd = buffer.indexOf(Buffer.from("\r\n\r\n"), start);
    if (headerEnd === -1) break;

    const headersRaw = buffer.slice(start, headerEnd).toString("utf8");
    let end = buffer.indexOf(boundary, headerEnd + 4);
    if (end === -1) break;
    if (buffer[end - 2] === 13 && buffer[end - 1] === 10) end -= 2;

    const disposition = /content-disposition:\s*form-data;([^\r\n]+)/i.exec(headersRaw)?.[1] || "";
    const name = /name="([^"]+)"/i.exec(disposition)?.[1] || "";
    const filename = /filename="([^"]*)"/i.exec(disposition)?.[1] || "";
    parts.push({ name, filename, data: buffer.slice(headerEnd + 4, end) });

    start = buffer.indexOf(boundary, end);
  }

  return parts;
}

function safeUploadPath(filename) {
  const normalized = path
    .normalize(filename.replace(/\\/g, "/"))
    .replace(/^([/\\])+/, "")
    .replace(/^(\.\.[/\\])+/, "");
  const segments = normalized.split(/[\\/]+/).filter(Boolean);
  const buildIndex = segments.lastIndexOf("web");
  const relativeSegments = buildIndex >= 0 ? segments.slice(buildIndex + 1) : segments;
  return relativeSegments.join(path.sep);
}

function saveGameFiles(slug, files) {
  const safeSlug = slugify(slug);
  if (!safeSlug) throw new Error("Missing slug");

  const targetRoot = path.join(ROOT, "assets", "games", safeSlug);
  fs.mkdirSync(targetRoot, { recursive: true });

  let saved = 0;
  for (const file of files) {
    const relativePath = safeUploadPath(file.filename);
    if (!relativePath || relativePath.endsWith(path.sep)) continue;
    const target = path.join(targetRoot, relativePath);
    if (!target.startsWith(targetRoot)) throw new Error("Invalid file path");
    fs.mkdirSync(path.dirname(target), { recursive: true });
    fs.writeFileSync(target, file.data);
    saved += 1;
  }

  if (!fs.existsSync(path.join(targetRoot, "index.html"))) {
    throw new Error("Upload must include index.html from Flutter build/web");
  }

  const indexPath = path.join(targetRoot, "index.html");
  const indexHtml = fs.readFileSync(indexPath, "utf8");
  const baseHref = `/assets/games/${safeSlug}/`;
  const nextHtml = indexHtml.includes("<base ")
    ? indexHtml.replace(/<base\s+href=["'][^"']*["']\s*>/i, `<base href="${baseHref}">`)
    : indexHtml.replace(/<head>/i, `<head>\n  <base href="${baseHref}">`);
  fs.writeFileSync(indexPath, nextHtml, "utf8");

  return { url: `/assets/games/${safeSlug}/index.html`, saved };
}

function layout({ title, description, canonical, image = "/assets/images/hero-bau-cua.png", body, schema = "", bodyClass = "", robots = "index,follow" }) {
  const safeTitle = escapeHtml(title);
  const safeDescription = escapeHtml(description);
  const safeCanonical = escapeHtml(canonical || "/");
  const safeImage = escapeHtml(image);

  return `<!doctype html>
<html lang="vi">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>${safeTitle}</title>
    <meta name="description" content="${safeDescription}" />
    <meta name="robots" content="${escapeHtml(robots)}" />
    <link rel="canonical" href="${safeCanonical}" />
    <meta property="og:type" content="website" />
    <meta property="og:title" content="${safeTitle}" />
    <meta property="og:description" content="${safeDescription}" />
    <meta property="og:image" content="${safeImage}" />
    <meta name="twitter:card" content="summary_large_image" />
    <link rel="preload" href="/assets/images/hero-bau-cua.png" as="image" />
    <link rel="stylesheet" href="/styles.css" />
    ${schema}
  </head>
  <body class="${escapeHtml(bodyClass)}">${body}</body>
</html>`;
}

function header({ admin = false } = {}) {
  return `<header class="site-header">
    <a class="brand" href="/" aria-label="Bầu Cua"><span>Bầu Cua</span></a>
    <button class="menu-toggle" type="button" aria-label="Mở menu" aria-expanded="false">☰</button>
    <nav class="main-nav" aria-label="Menu chính">
      <a href="/">Trang chủ</a>
      <a href="/#games">Games</a>
      <a href="/bai-viet">Bài viết</a>
      <a href="/huong-dan">Hướng dẫn</a>
      <a href="/gioi-thieu">Giới thiệu</a>
      ${admin ? '<a href="/admin/logout">Đăng xuất</a>' : ""}
    </nav>
  </header>`;
}

function stars(rating) {
  const count = Math.round(Number(rating || 0));
  return "★★★★★".slice(0, count).padEnd(5, "☆");
}

function gameCard(game) {
  return `<a class="game-card" href="/game/${escapeHtml(game.slug)}" aria-label="Xem chi tiết ${escapeHtml(game.title)}">
    <img src="${escapeHtml(game.image)}" alt="${escapeHtml(game.title)}" loading="lazy" />
    <div>
      <h3>${escapeHtml(game.title)}</h3>
      <p>${escapeHtml(game.subtitle)}</p>
      <div class="rating">${stars(game.rating)} <span>${Number(game.rating).toFixed(1)}/5</span></div>
      <span class="detail-link">Chi tiết game</span>
    </div>
  </a>`;
}

function jsonLd(data) {
  return `<script type="application/ld+json">${JSON.stringify(data)}</script>`;
}

function siteFooter() {
  return `<footer class="site-footer">
    <div><strong>Bầu Cua</strong><span>Game bầu cua online miễn phí, chơi trực tiếp trên web.</span></div>
    <nav aria-label="Liên kết chân trang">
      <a href="/huong-dan">Hướng dẫn</a>
      <a href="/bai-viet">Bài viết</a>
      <a href="/gioi-thieu">Giới thiệu</a>
      <a href="/chinh-sach-bao-mat">Chính sách bảo mật</a>
    </nav>
  </footer>`;
}

function contentPage(req, page) {
  const pages = {
    "/huong-dan": {
      title: "Hướng dẫn chơi bầu cua online miễn phí | Bầu Cua",
      description: "Hướng dẫn cách mở game bầu cua online, xem chi tiết game và chơi trực tiếp trên điện thoại hoặc máy tính.",
      h1: "Hướng dẫn chơi bầu cua online",
      intro: "Trang này hướng dẫn cách sử dụng website Bầu Cua để tìm game, xem thông tin và mở game trực tiếp trên trình duyệt.",
      sections: [
        ["Chọn game bầu cua", "Tại trang chủ, người chơi chọn một game trong danh sách. Mỗi thẻ game có tên, mô tả ngắn, đánh giá và nút xem chi tiết."],
        ["Xem thông tin trước khi chơi", "Trang chi tiết game hiển thị mô tả, đánh giá, trạng thái miễn phí và nút chơi ngay. Đây là nơi phù hợp để hiểu game trước khi mở màn hình chơi."],
        ["Chơi trực tiếp trên web", "Sau khi bấm chơi ngay, game sẽ mở trong trình duyệt. Website ưu tiên trải nghiệm mobile, không yêu cầu tải ứng dụng và không có chức năng nạp rút tiền."],
      ],
    },
    "/gioi-thieu": {
      title: "Giới thiệu Bầu Cua - Cổng game bầu cua giải trí",
      description: "Bầu Cua là website tổng hợp game bầu cua giải trí miễn phí, tối ưu cho điện thoại và chơi trực tiếp trên web.",
      h1: "Giới thiệu Bầu Cua",
      intro: "Bầu Cua được xây dựng như một cổng game giải trí đơn giản, tập trung vào các phiên bản bầu cua chạy trực tiếp trên web.",
      sections: [
        ["Mục tiêu của website", "Website ưu tiên sự gọn nhẹ, dễ dùng và rõ ràng. Người chơi có thể xem danh sách game, đọc mô tả, đánh giá và mở game nhanh trên điện thoại."],
        ["Game giải trí miễn phí", "Các game trên website phục vụ mục đích giải trí, không cung cấp nạp tiền, rút tiền, đổi thưởng hay các giao dịch tài chính."],
        ["Cập nhật thêm game mới", "Khi có phiên bản Flutter web mới, quản trị viên có thể thêm game vào hệ thống để người chơi truy cập qua trang chi tiết riêng."],
      ],
    },
    "/chinh-sach-bao-mat": {
      title: "Chính sách bảo mật | Bầu Cua",
      description: "Chính sách bảo mật của website Bầu Cua, mô tả cách website vận hành và bảo vệ trải nghiệm người dùng.",
      h1: "Chính sách bảo mật",
      intro: "Website Bầu Cua hướng đến trải nghiệm chơi game giải trí đơn giản, không yêu cầu người chơi nạp rút tiền.",
      sections: [
        ["Thông tin người dùng", "Website không yêu cầu người chơi tạo tài khoản để mở game. Trang admin chỉ dành cho quản trị viên và được bảo vệ bằng tài khoản đăng nhập."],
        ["Dữ liệu game", "Thông tin game như tên, mô tả, ảnh, đánh giá và đường dẫn được lưu để hiển thị nội dung trên website."],
        ["Liên hệ quản trị", "Nếu cần điều chỉnh nội dung hoặc báo lỗi game, người quản trị có thể cập nhật thông tin qua trang admin."],
      ],
    },
  };
  const data = pages[page];
  if (!data) return null;

  const schema = jsonLd({
    "@context": "https://schema.org",
    "@type": "WebPage",
    name: data.h1,
    description: data.description,
    url: absoluteUrl(req, page),
    inLanguage: "vi-VN",
  });
  const body = `${header()}
  <main class="content-page">
    <article class="content-card">
      <p class="eyebrow">Bầu Cua</p>
      <h1>${escapeHtml(data.h1)}</h1>
      <p class="lead">${escapeHtml(data.intro)}</p>
      ${data.sections.map(([title, text]) => `<section><h2>${escapeHtml(title)}</h2><p>${escapeHtml(text)}</p></section>`).join("")}
    </article>
  </main>
  ${siteFooter()}
  <script src="/site.js"></script>`;

  return layout({
    title: data.title,
    description: data.description,
    canonical: absoluteUrl(req, page),
    body,
    schema,
  });
}

function postsPage(req) {
  const posts = activePosts();
  const body = `${header()}
  <main class="posts-page">
    <header class="posts-heading">
      <p class="eyebrow">Kiến thức bầu cua</p>
      <h1>Bài viết và hướng dẫn</h1>
      <p>Tổng hợp nội dung hướng dẫn chơi, thông tin trò chơi dân gian và các cập nhật mới từ Bầu Cua.</p>
    </header>
    <section class="posts-grid">
      ${posts.length
        ? posts.map((post) => `<article class="post-card">
          <a href="/bai-viet/${escapeHtml(post.slug)}">
            <img src="${escapeHtml(post.image || "/assets/images/hero-bau-cua.png")}" alt="${escapeHtml(post.title)}" loading="lazy" />
            <div><time datetime="${escapeHtml(post.updatedAt || post.createdAt)}">${escapeHtml(new Date(post.updatedAt || post.createdAt).toLocaleDateString("vi-VN"))}</time><h2>${escapeHtml(post.title)}</h2><p>${escapeHtml(post.excerpt)}</p><span>Đọc bài viết</span></div>
          </a>
        </article>`).join("")
        : "<p>Chưa có bài viết được xuất bản.</p>"}
    </section>
  </main>
  ${siteFooter()}
  <script src="/site.js"></script>`;

  return layout({
    title: "Bài viết về bầu cua - Hướng dẫn và kiến thức",
    description: "Tổng hợp bài viết, hướng dẫn chơi bầu cua online và kiến thức về trò chơi bầu cua dân gian.",
    canonical: absoluteUrl(req, "/bai-viet"),
    body,
  });
}

function postPage(req, post) {
  const canonical = `/bai-viet/${post.slug}`;
  const schema = [
    {
      "@context": "https://schema.org",
      "@type": "Article",
      headline: post.title,
      description: post.description,
      image: absoluteUrl(req, post.image || "/assets/images/hero-bau-cua.png"),
      datePublished: post.createdAt,
      dateModified: post.updatedAt,
      mainEntityOfPage: absoluteUrl(req, canonical),
      author: { "@type": "Organization", name: "Bầu Cua" },
      publisher: { "@type": "Organization", name: "Bầu Cua" },
    },
    {
      "@context": "https://schema.org",
      "@type": "BreadcrumbList",
      itemListElement: [
        { "@type": "ListItem", position: 1, name: "Trang chủ", item: absoluteUrl(req, "/") },
        { "@type": "ListItem", position: 2, name: "Bài viết", item: absoluteUrl(req, "/bai-viet") },
        { "@type": "ListItem", position: 3, name: post.title, item: absoluteUrl(req, canonical) },
      ],
    },
  ].map(jsonLd).join("");
  const body = `${header()}
  <main class="post-page">
    <article class="post-article">
      <header>
        <p class="eyebrow">Bài viết bầu cua</p>
        <h1>${escapeHtml(post.title)}</h1>
        <p class="post-excerpt">${escapeHtml(post.excerpt)}</p>
        <time datetime="${escapeHtml(post.updatedAt || post.createdAt)}">Cập nhật ${escapeHtml(new Date(post.updatedAt || post.createdAt).toLocaleDateString("vi-VN"))}</time>
      </header>
      ${post.image ? `<img class="post-cover" src="${escapeHtml(post.image)}" alt="${escapeHtml(post.title)}" />` : ""}
      <div class="post-content">${sanitizePostHtml(post.content)}</div>
    </article>
  </main>
  ${siteFooter()}
  <script src="/site.js"></script>`;

  return layout({
    title: `${post.title} | Bầu Cua`,
    description: post.description || post.excerpt,
    canonical: absoluteUrl(req, canonical),
    image: absoluteUrl(req, post.image || "/assets/images/hero-bau-cua.png"),
    body,
    schema,
  });
}

function homePage(req) {
  const games = activeGames();
  const schema = [
    {
      "@context": "https://schema.org",
      "@type": "WebSite",
      name: "Bầu Cua",
      url: absoluteUrl(req, "/"),
      inLanguage: "vi-VN",
      description: "Cổng game bầu cua online miễn phí, chơi trực tiếp trên web và tối ưu cho điện thoại.",
    },
    {
      "@context": "https://schema.org",
      "@type": "Organization",
      name: "Bầu Cua",
      url: absoluteUrl(req, "/"),
      logo: absoluteUrl(req, "/assets/images/hero-bau-cua.png"),
    },
    {
      "@context": "https://schema.org",
      "@type": "FAQPage",
      mainEntity: [
        {
          "@type": "Question",
          name: "Bầu cua online trên website này có miễn phí không?",
          acceptedAnswer: {
            "@type": "Answer",
            text: "Có. Các game bầu cua trên website được xây dựng cho mục đích giải trí, chơi trực tiếp trên trình duyệt và không có nạp rút tiền.",
          },
        },
        {
          "@type": "Question",
          name: "Có cần tải ứng dụng để chơi bầu cua không?",
          acceptedAnswer: {
            "@type": "Answer",
            text: "Không cần tải ứng dụng. Người chơi có thể mở game bầu cua bằng trình duyệt trên điện thoại hoặc máy tính.",
          },
        },
      ],
    },
  ].map(jsonLd).join("");

  const body = `${header()}
  <main class="one-screen home-page" id="home">
    <section class="hero">
      <div class="hero-copy">
        <h1>Bầu Cua</h1>
        <p>Cổng game bầu cua online miễn phí, chơi trực tiếp trên web không cần tải về. Giao diện đẹp, tối ưu cho điện thoại.</p>
      </div>
      <img class="hero-image" src="/assets/images/hero-bau-cua.png" alt="Xúc xắc bầu cua trên khay vàng" />
    </section>
    <section class="games-panel" id="games">
      <div class="section-title"><h2>Game Bầu Cua Hấp Dẫn</h2></div>
      <div class="game-grid">${games.map(gameCard).join("")}</div>
    </section>
    <section class="feature-band" aria-label="Lợi ích">
      <article><strong>Miễn phí 100%</strong><span>Không nạp rút tiền</span></article>
      <article><strong>Chơi ngay trên web</strong><span>Không cần tải về</span></article>
      <article><strong>Tương thích mobile</strong><span>Chơi tốt trên điện thoại</span></article>
      <article><strong>Cập nhật game mới</strong><span>Quản lý qua admin</span></article>
    </section>
  </main>
  <section class="seo-section">
    <div class="seo-copy">
      <p class="eyebrow">Bầu cua online</p>
      <h2>Chơi bầu cua miễn phí trên trình duyệt</h2>
      <p>Bầu Cua là cổng game bầu cua giải trí, tập trung vào trải nghiệm nhanh, dễ mở và phù hợp với điện thoại. Người chơi có thể chọn một phiên bản bầu cua trong danh sách, xem thông tin chi tiết rồi bấm chơi ngay mà không cần cài thêm ứng dụng.</p>
      <p>Website không cung cấp nạp rút tiền, không tổ chức cá cược và không yêu cầu giao dịch tài chính. Nội dung được xây dựng theo hướng giải trí nhẹ, dễ dùng, tải nhanh và có trang chi tiết riêng cho từng game để người chơi hiểu rõ trước khi mở game.</p>
    </div>
    <div class="seo-grid">
      <article><h3>Bầu cua là gì?</h3><p>Bầu cua là trò chơi dân gian quen thuộc với các biểu tượng như bầu, cua, tôm, cá, gà, nai. Phiên bản trên web mô phỏng lại trải nghiệm này bằng hình ảnh và âm thanh trên trình duyệt.</p></article>
      <article><h3>Chơi trên điện thoại</h3><p>Giao diện website ưu tiên mobile, danh sách game cuộn dọc, nút thao tác lớn và trang chơi game mở toàn màn hình để phù hợp với màn hình nhỏ.</p></article>
      <article><h3>Cập nhật game mới</h3><p>Khi có game Flutter web mới, quản trị viên có thể thêm thông tin, ảnh đại diện, mô tả SEO và đường dẫn game qua trang admin.</p></article>
    </div>
  </section>
  <section class="seo-section seo-faq">
    <div class="section-title"><h2>Câu hỏi thường gặp về bầu cua</h2></div>
    <details open><summary>Website có phải game đổi thưởng không?</summary><p>Không. Website chỉ phục vụ giải trí, không có chức năng nạp tiền, rút tiền hay đổi thưởng.</p></details>
    <details><summary>Tôi có thể chơi bầu cua không cần tải app không?</summary><p>Có. Các game được nhúng để chạy trực tiếp trên web, phù hợp với trình duyệt hiện đại trên điện thoại và máy tính.</p></details>
    <details><summary>Vì sao mỗi game có trang chi tiết riêng?</summary><p>Trang chi tiết giúp người chơi xem mô tả, đánh giá, ảnh đại diện và nút chơi ngay. Đây cũng là cấu trúc tốt hơn cho SEO vì mỗi game có một URL rõ ràng.</p></details>
  </section>
  ${siteFooter()}
  <script src="/site.js"></script>`;

  return layout({
    title: "Bầu Cua Online - Chơi bầu cua miễn phí trên web",
    description: "Chơi bầu cua online miễn phí trên trình duyệt, tối ưu cho điện thoại, không cần tải app và không có nạp rút tiền.",
    canonical: absoluteUrl(req, "/"),
    image: absoluteUrl(req, "/assets/images/hero-bau-cua.png"),
    body,
    schema,
    bodyClass: "home-page",
  });
}

function detailPage(req, game) {
  const schema = [
    {
      "@context": "https://schema.org",
      "@type": "Game",
      name: game.title,
      description: game.description,
      image: absoluteUrl(req, game.image),
      url: absoluteUrl(req, `/game/${game.slug}`),
      inLanguage: "vi-VN",
      genre: "Bầu cua online",
      aggregateRating: {
        "@type": "AggregateRating",
        ratingValue: game.rating,
        ratingCount: game.reviewCount,
        bestRating: 5,
        worstRating: 1,
      },
      offers: {
        "@type": "Offer",
        price: "0",
        priceCurrency: "VND",
        availability: "https://schema.org/InStock",
      },
    },
    {
      "@context": "https://schema.org",
      "@type": "BreadcrumbList",
      itemListElement: [
        { "@type": "ListItem", position: 1, name: "Trang chủ", item: absoluteUrl(req, "/") },
        { "@type": "ListItem", position: 2, name: game.title, item: absoluteUrl(req, `/game/${game.slug}`) },
      ],
    },
  ].map(jsonLd).join("");

  const body = `${header()}
  <main class="detail-page">
    <article class="detail-hero">
      <img src="${escapeHtml(game.image)}" alt="${escapeHtml(game.title)}" />
      <div>
        <p class="eyebrow">Game bầu cua miễn phí</p>
        <h1>${escapeHtml(game.title)}</h1>
        <p>${escapeHtml(game.description)}</p>
        <div class="rating big">${stars(game.rating)} <span>${Number(game.rating).toFixed(1)}/5 từ ${Number(game.reviewCount)} đánh giá</span></div>
        <a class="btn primary" href="/play/${escapeHtml(game.slug)}">▶ Chơi ngay</a>
      </div>
    </article>
    <section class="detail-content">
      <h2>Thông tin game</h2>
      <p>${escapeHtml(game.title)} là một phiên bản bầu cua online dành cho người chơi muốn mở game nhanh trên trình duyệt. Game phù hợp để giải trí ngắn, thao tác đơn giản và có thể chơi trên điện thoại mà không cần cài ứng dụng.</p>
      <ul>
        <li>Chơi trực tiếp trên web, không cần tải ứng dụng.</li>
        <li>Tối ưu cho điện thoại và trình duyệt hiện đại.</li>
        <li>Game giải trí miễn phí, không nạp rút tiền.</li>
      </ul>
      <h2>Đánh giá</h2>
      <p>${escapeHtml(game.title)} được người chơi đánh giá ${Number(game.rating).toFixed(1)}/5 nhờ giao diện dễ nhìn, tải nhanh và thao tác đơn giản.</p>
      <h2>Cách chơi trên web</h2>
      <p>Bấm nút chơi ngay để mở ${escapeHtml(game.title)} trong màn hình chơi riêng. Nếu game tải chậm, hãy kiểm tra kết nối mạng hoặc mở lại trang bằng trình duyệt mới nhất trên điện thoại.</p>
      <h2>Câu hỏi thường gặp</h2>
      <details open><summary>${escapeHtml(game.title)} có miễn phí không?</summary><p>Có. Game phục vụ mục đích giải trí miễn phí và không có nạp rút tiền.</p></details>
      <details><summary>Có cần cài app để chơi không?</summary><p>Không. Game chạy trực tiếp trên trình duyệt qua phiên bản Flutter web.</p></details>
      <p><a class="text-link" href="/#games">Xem thêm các game bầu cua online khác</a></p>
    </section>
  </main>
  ${siteFooter()}
  <script src="/site.js"></script>`;

  return layout({
    title: `${game.title} - Chơi bầu cua online miễn phí`,
    description: `${game.title}: ${game.subtitle}. Chơi bầu cua online miễn phí trực tiếp trên web, tối ưu cho điện thoại, không cần tải app.`,
    canonical: absoluteUrl(req, `/game/${game.slug}`),
    image: absoluteUrl(req, game.image),
    body,
    schema,
  });
}

function playPage(game) {
  return `<!doctype html>
<html lang="vi">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
    <title>${escapeHtml(game.title)} - Bầu Cua</title>
    <meta name="robots" content="noindex" />
    <link rel="stylesheet" href="/styles.css" />
  </head>
  <body class="play-page">
    <a class="play-back" href="/game/${escapeHtml(game.slug)}" aria-label="Quay lại">‹</a>
    <main class="play-stage">
      <div class="game-loading"><strong>Đang mở game</strong><span>Game sẽ chạy trực tiếp trong trình duyệt.</span></div>
      <iframe id="game-frame" title="${escapeHtml(game.title)}" src="${escapeHtml(game.url)}" allowfullscreen></iframe>
    </main>
    <script>document.getElementById("game-frame").addEventListener("load",()=>document.querySelector(".game-loading").remove());</script>
  </body>
</html>`;
}

function adminLoginPage(error = "") {
  const body = `${header()}
  <main class="login-shell">
    <form class="login-panel" method="post" action="/admin/login">
      <h1>Đăng nhập admin</h1>
      ${error ? `<p class="form-error">${escapeHtml(error)}</p>` : ""}
      <label>Tài khoản<input name="username" autocomplete="username" required /></label>
      <label>Mật khẩu<input name="password" type="password" autocomplete="current-password" required /></label>
      <button class="btn primary" type="submit">Đăng nhập</button>
      <p class="login-note">Mặc định: admin / admin123. Khi deploy nên đổi bằng biến môi trường.</p>
    </form>
  </main>
  <script src="/site.js"></script>`;

  return layout({
    title: "Admin - Bầu Cua",
    description: "Trang đăng nhập quản trị Bầu Cua.",
    canonical: "/admin",
    body,
    robots: "noindex,nofollow",
  });
}

function adminSidebar(active) {
  const links = [
    ["/admin", "dashboard", "Tổng quan"],
    ["/admin/games", "games", "Quản lý game"],
    ["/admin/games/new", "game-new", "Thêm game"],
    ["/admin/posts", "posts", "Bài viết"],
    ["/admin/posts/new", "post-new", "Viết bài mới"],
  ];
  return `<aside class="admin-sidebar">
    <a class="admin-brand" href="/admin"><span>Bầu Cua</span><small>Admin Panel</small></a>
    <nav class="admin-nav" aria-label="Admin menu">
      ${links.map(([href, key, label]) => `<a class="${active === key ? "active" : ""}" href="${href}">${label}</a>`).join("")}
      <a href="/" target="_blank" rel="noreferrer">Xem website</a>
    </nav>
    <a class="admin-logout" href="/admin/logout">Đăng xuất</a>
  </aside>`;
}

function gameAdminForm() {
  return `<section class="admin-panel admin-form-panel">
    <div class="admin-panel-head"><div><p>Game</p><h2>Thêm hoặc sửa game</h2></div></div>
    <form id="game-form" class="game-form">
      <input type="hidden" id="game-id" />
      <label>Tên game<input id="title" required placeholder="Bầu Cua Dân Gian" /></label>
      <div class="form-row">
        <label>Slug<input id="slug" required placeholder="bau-cua-dan-gian" /></label>
        <label>Thứ tự<input id="sort" type="number" min="1" value="1" /></label>
      </div>
      <label>Mô tả ngắn<input id="subtitle" required placeholder="Bầu cua truyền thống" /></label>
      <label>Mô tả SEO<textarea id="description" required placeholder="Mô tả chi tiết cho trang game"></textarea></label>
      <label>Ảnh thumbnail<input id="image" required placeholder="/assets/images/game-dan-gian.png" /></label>
      <label>Đường dẫn game Flutter Web<input id="url" required placeholder="/assets/games/bau-cua-dan-gian/index.html" /></label>
      <label>Upload thư mục Flutter build/web<input id="gameFiles" type="file" webkitdirectory directory multiple /></label>
      <p class="form-help">Chọn thư mục build/web của Flutter. Server sẽ lưu vào assets/games/slug và tự sửa base href.</p>
      <div class="form-row">
        <label>Đánh giá<input id="rating" type="number" min="1" max="5" step="0.1" value="4.5" /></label>
        <label>Lượt đánh giá<input id="reviewCount" type="number" min="1" value="50" /></label>
      </div>
      <div class="form-row">
        <label>Trạng thái<select id="active"><option value="true">Hiển thị</option><option value="false">Ẩn</option></select></label>
        <label class="inline-check"><input id="hot" type="checkbox" /> Game hot</label>
      </div>
      <div class="admin-actions">
        <button class="btn primary" type="submit">Lưu game</button>
        <a class="btn secondary" href="/admin/games">Hủy</a>
      </div>
    </form>
  </section>`;
}

function postAdminForm() {
  return `<section class="admin-panel admin-form-panel">
    <div class="admin-panel-head"><div><p>Nội dung SEO</p><h2>Viết hoặc sửa bài</h2></div></div>
    <form id="post-form" class="game-form">
      <input type="hidden" id="post-id" />
      <label>Tiêu đề bài viết<input id="post-title" required placeholder="Bầu cua là gì?" /></label>
      <label>Slug<input id="post-slug" required placeholder="bau-cua-la-gi" /></label>
      <label>Mô tả ngắn<input id="post-excerpt" required placeholder="Đoạn giới thiệu hiển thị trong danh sách bài viết" /></label>
      <label>Mô tả SEO<textarea id="post-description" required placeholder="Mô tả khoảng 140-160 ký tự cho Google"></textarea></label>
      <label>Ảnh đại diện<input id="post-image" placeholder="/assets/images/hero-bau-cua.png" /></label>
      <div class="classic-editor">
        <div class="editor-tabs"><button type="button" class="active" data-editor-mode="visual">Trực quan</button><button type="button" data-editor-mode="html">HTML</button></div>
        <div class="editor-toolbar" aria-label="Công cụ soạn thảo">
          <button type="button" data-command="formatBlock" data-value="p">Đoạn</button><button type="button" data-command="formatBlock" data-value="h2">H2</button><button type="button" data-command="formatBlock" data-value="h3">H3</button><button type="button" data-command="bold"><strong>B</strong></button><button type="button" data-command="italic"><em>I</em></button><button type="button" data-command="insertUnorderedList">Danh sách</button><button type="button" data-command="createLink">Liên kết</button><button type="button" data-command="insertImage">Ảnh</button><button type="button" data-command="removeFormat">Xóa định dạng</button>
        </div>
        <div id="post-editor" class="editor-visual" contenteditable="true" aria-label="Nội dung bài viết"></div>
        <textarea id="post-content" class="editor-html" aria-label="Nội dung HTML"></textarea>
      </div>
      <label>Trạng thái<select id="post-active"><option value="true">Xuất bản</option><option value="false">Bản nháp</option></select></label>
      <div class="admin-actions"><button class="btn primary" type="submit">Lưu bài viết</button><a class="btn secondary" href="/admin/posts">Hủy</a></div>
    </form>
  </section>`;
}

function adminPage(page = "dashboard") {
  const db = readDb();
  const games = db.games;
  const posts = db.posts;
  const visibleCount = games.filter((game) => game.active).length;
  const hotCount = games.filter((game) => game.hot).length;
  const pages = {
    dashboard: {
      title: "Tổng quan",
      content: `<section class="admin-stats" aria-label="Thống kê nhanh">
        <article><span>Tổng game</span><strong>${games.length}</strong></article><article><span>Đang hiển thị</span><strong>${visibleCount}</strong></article><article><span>Game hot</span><strong>${hotCount}</strong></article><article><span>Bài viết</span><strong>${posts.length}</strong></article>
      </section>
      <section class="admin-panel dashboard-actions"><div class="admin-panel-head"><div><p>Thao tác nhanh</p><h2>Quản lý nội dung</h2></div></div><div class="dashboard-links"><a href="/admin/games">Quản lý game</a><a href="/admin/games/new">Thêm game mới</a><a href="/admin/posts">Quản lý bài viết</a><a href="/admin/posts/new">Viết bài mới</a></div></section>`,
    },
    games: { title: "Quản lý game", content: `<section class="admin-panel"><div class="admin-panel-head"><div><p>Database</p><h2>Game hiện có</h2></div><a class="btn primary" href="/admin/games/new">Thêm game</a></div><div class="admin-list" id="admin-list"></div></section>` },
    "game-new": { title: "Thêm hoặc sửa game", content: gameAdminForm() },
    posts: { title: "Quản lý bài viết", content: `<section class="admin-panel"><div class="admin-panel-head"><div><p>Content</p><h2>Bài viết hiện có</h2></div><a class="btn primary" href="/admin/posts/new">Viết bài mới</a></div><div class="admin-list" id="post-list"></div></section>` },
    "post-new": { title: "Viết hoặc sửa bài", content: postAdminForm() },
  };
  const current = pages[page] || pages.dashboard;
  const body = `<main class="admin-layout">
    ${adminSidebar(page)}
    <section class="admin-main">
      <header class="admin-topbar"><div><p>Quản trị nội dung</p><h1>${current.title}</h1></div><a class="btn secondary" href="/" target="_blank" rel="noreferrer">Mở website</a></header>
      ${current.content}
    </section>
  </main>
  <script src="/admin.js"></script>`;

  return layout({
    title: `${current.title} - Admin Bầu Cua`,
    description: `${current.title} trong trang quản trị Bầu Cua.`,
    canonical: "/admin",
    body,
    robots: "noindex,nofollow",
  });
}

function absoluteUrl(req, pathname) {
  const protocol = req.headers["x-forwarded-proto"] || "http";
  return `${protocol}://${req.headers.host}${pathname}`;
}

function serveStatic(req, res, pathname) {
  const safePath = path.normalize(decodeURIComponent(pathname)).replace(/^(\.\.[/\\])+/, "");
  const filePath = path.join(ROOT, safePath);
  if (!filePath.startsWith(ROOT) || !fs.existsSync(filePath) || fs.statSync(filePath).isDirectory()) {
    return false;
  }
  const ext = path.extname(filePath).toLowerCase();
  send(res, 200, fs.readFileSync(filePath), MIME_TYPES[ext] || "application/octet-stream");
  return true;
}

function normalizeGame(input, existingId) {
  const title = String(input.title || "").trim();
  const slug = slugify(input.slug || title);
  return {
    id: existingId || input.id || crypto.randomUUID(),
    title,
    slug,
    subtitle: String(input.subtitle || "").trim(),
    description: String(input.description || "").trim(),
    image: String(input.image || "").trim(),
    url: String(input.url || "").trim(),
    rating: Math.max(1, Math.min(5, Number(input.rating || 4.5))),
    reviewCount: Math.max(1, Number(input.reviewCount || 1)),
    sort: Number(input.sort || 1),
    hot: Boolean(input.hot),
    active: Boolean(input.active),
  };
}

function normalizePost(input, existing = {}) {
  const now = new Date().toISOString();
  const title = String(input.title || "").trim();
  const content = sanitizePostHtml(input.content);
  return {
    id: existing.id || input.id || crypto.randomUUID(),
    title,
    slug: slugify(input.slug || title),
    excerpt: String(input.excerpt || "").trim(),
    description: String(input.description || "").trim(),
    image: String(input.image || "").trim(),
    content,
    active: Boolean(input.active),
    createdAt: existing.createdAt || now,
    updatedAt: now,
  };
}

async function handleApi(req, res, pathname) {
  if (!isAuthed(req)) return json(res, 401, { error: "Unauthorized" });

  const db = readDb();
  if (req.method === "GET" && pathname === "/api/games") return json(res, 200, db.games);
  if (req.method === "GET" && pathname === "/api/posts") return json(res, 200, db.posts);

  if (req.method === "POST" && pathname === "/api/games/upload") {
    const parts = parseMultipart(await readBuffer(req), req.headers["content-type"]);
    const slug = parts.find((part) => part.name === "slug")?.data.toString("utf8").trim();
    const files = parts.filter((part) => part.name === "files" && part.filename);
    if (!slug || files.length === 0) return json(res, 400, { error: "Missing slug or files" });
    return json(res, 200, saveGameFiles(slug, files));
  }

  if (req.method === "POST" && pathname === "/api/games") {
    const input = JSON.parse(await readBody(req));
    const game = normalizeGame(input);
    if (!game.title || !game.slug || !game.url) return json(res, 400, { error: "Missing required fields" });
    if (db.games.some((item) => item.slug === game.slug)) return json(res, 409, { error: "Slug already exists" });
    db.games.push(game);
    writeDb(db);
    return json(res, 201, game);
  }

  const match = pathname.match(/^\/api\/games\/([^/]+)$/);
  if (match && req.method === "PUT") {
    const index = db.games.findIndex((game) => game.id === match[1]);
    if (index === -1) return json(res, 404, { error: "Game not found" });
    const game = normalizeGame(JSON.parse(await readBody(req)), db.games[index].id);
    if (db.games.some((item) => item.id !== game.id && item.slug === game.slug)) {
      return json(res, 409, { error: "Slug already exists" });
    }
    db.games[index] = game;
    writeDb(db);
    return json(res, 200, db.games[index]);
  }

  if (match && req.method === "DELETE") {
    const nextGames = db.games.filter((game) => game.id !== match[1]);
    if (nextGames.length === db.games.length) return json(res, 404, { error: "Game not found" });
    db.games = nextGames;
    writeDb(db);
    return json(res, 200, { ok: true });
  }

  if (req.method === "POST" && pathname === "/api/posts") {
    const post = normalizePost(JSON.parse(await readBody(req)));
    if (!post.title || !post.slug || !post.excerpt || !stripHtml(post.content)) {
      return json(res, 400, { error: "Missing required post fields" });
    }
    if (db.posts.some((item) => item.slug === post.slug)) return json(res, 409, { error: "Slug already exists" });
    db.posts.push(post);
    writeDb(db);
    return json(res, 201, post);
  }

  const postMatch = pathname.match(/^\/api\/posts\/([^/]+)$/);
  if (postMatch && req.method === "PUT") {
    const index = db.posts.findIndex((post) => post.id === postMatch[1]);
    if (index === -1) return json(res, 404, { error: "Post not found" });
    const post = normalizePost(JSON.parse(await readBody(req)), db.posts[index]);
    if (!post.title || !post.slug || !post.excerpt || !stripHtml(post.content)) {
      return json(res, 400, { error: "Missing required post fields" });
    }
    if (db.posts.some((item) => item.id !== post.id && item.slug === post.slug)) {
      return json(res, 409, { error: "Slug already exists" });
    }
    db.posts[index] = post;
    writeDb(db);
    return json(res, 200, post);
  }

  if (postMatch && req.method === "DELETE") {
    const nextPosts = db.posts.filter((post) => post.id !== postMatch[1]);
    if (nextPosts.length === db.posts.length) return json(res, 404, { error: "Post not found" });
    db.posts = nextPosts;
    writeDb(db);
    return json(res, 200, { ok: true });
  }

  return json(res, 404, { error: "Not found" });
}

async function handle(req, res) {
  try {
    const url = new URL(req.url, `http://${req.headers.host}`);
    const pathname = url.pathname;

    if (pathname.startsWith("/assets/") || pathname === "/styles.css" || pathname === "/site.js" || pathname === "/admin.js") {
      if (serveStatic(req, res, pathname)) return;
    }

    if (pathname.startsWith("/api/")) return handleApi(req, res, pathname);
    if (req.method === "GET" && pathname === "/robots.txt") {
      return send(res, 200, `User-agent: *\nAllow: /\nDisallow: /admin\nDisallow: /api/\nSitemap: ${absoluteUrl(req, "/sitemap.xml")}\n`, "text/plain; charset=utf-8");
    }
    if (req.method === "GET" && pathname === "/sitemap.xml") {
      const urls = [
        "/",
        "/bai-viet",
        "/huong-dan",
        "/gioi-thieu",
        "/chinh-sach-bao-mat",
        ...activeGames().map((game) => `/game/${game.slug}`),
        ...activePosts().map((post) => `/bai-viet/${post.slug}`),
      ];
      const xml = `<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${urls
        .map((item) => `  <url><loc>${absoluteUrl(req, item)}</loc></url>`)
        .join("\n")}\n</urlset>`;
      return send(res, 200, xml, "application/xml; charset=utf-8");
    }
    if (req.method === "GET" && pathname === "/") return send(res, 200, homePage(req));
    if (req.method === "GET" && pathname === "/bai-viet") return send(res, 200, postsPage(req));

    if (req.method === "GET" && pathname.startsWith("/bai-viet/")) {
      const post = findPost(pathname.split("/").pop());
      return post ? send(res, 200, postPage(req, post)) : send(res, 404, "Không tìm thấy bài viết");
    }

    if (req.method === "GET" && ["/huong-dan", "/gioi-thieu", "/chinh-sach-bao-mat"].includes(pathname)) {
      return send(res, 200, contentPage(req, pathname));
    }

    if (req.method === "GET" && pathname.startsWith("/game/")) {
      const game = findGame(pathname.split("/").pop());
      return game ? send(res, 200, detailPage(req, game)) : send(res, 404, "Không tìm thấy game");
    }

    if (req.method === "GET" && pathname.startsWith("/play/")) {
      const game = findGame(pathname.split("/").pop());
      return game ? send(res, 200, playPage(game)) : send(res, 404, "Không tìm thấy game");
    }

    if (req.method === "GET" && pathname.startsWith("/admin") && pathname !== "/admin/logout") {
      if (!isAuthed(req)) return send(res, 200, adminLoginPage());
      const adminRoutes = {
        "/admin": "dashboard",
        "/admin/games": "games",
        "/admin/games/new": "game-new",
        "/admin/posts": "posts",
        "/admin/posts/new": "post-new",
      };
      return adminRoutes[pathname]
        ? send(res, 200, adminPage(adminRoutes[pathname]))
        : send(res, 404, "Không tìm thấy trang admin");
    }

    if (req.method === "POST" && pathname === "/admin/login") {
      const form = new URLSearchParams(await readBody(req));
      if (form.get("username") === ADMIN_USER && form.get("password") === ADMIN_PASSWORD) {
        const sessionId = crypto.randomBytes(24).toString("hex");
        sessions.add(sessionId);
        return redirectWithCookie(res, "/admin", sessionId);
      }
      return send(res, 401, adminLoginPage("Sai tài khoản hoặc mật khẩu."));
    }

    if (req.method === "GET" && pathname === "/admin/logout") {
      sessions.delete(parseCookies(req).admin_session);
      return send(res, 302, "", "text/plain", {
        Location: "/admin",
        "Set-Cookie": "admin_session=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0",
      });
    }

    return send(res, 404, "Không tìm thấy trang");
  } catch (error) {
    console.error(error);
    return send(res, 500, "Có lỗi máy chủ");
  }
}

function redirectWithCookie(res, location, sessionId) {
  send(res, 302, "", "text/plain", {
    Location: location,
    "Set-Cookie": `admin_session=${sessionId}; HttpOnly; SameSite=Lax; Path=/; Max-Age=86400`,
  });
}

http.createServer(handle).listen(PORT, () => {
  console.log(`Bầu Cua running at http://localhost:${PORT}`);
  console.log(`Admin: http://localhost:${PORT}/admin`);
});
