import assert from "node:assert/strict";
import test from "node:test";
import { strToU8, zipSync } from "fflate";
import { parseEpub } from "../../lib/client/parsers/epub.ts";

function makeEpub(overrides = {}) {
  const files = {
    mimetype: strToU8("application/epub+zip"),
    "META-INF/container.xml": strToU8(`<?xml version="1.0"?>
      <container xmlns="urn:oasis:names:tc:opendocument:xmlns:container" version="1.0">
        <rootfiles><rootfile full-path="OPS/package.opf" media-type="application/oebps-package+xml"/></rootfiles>
      </container>`),
    "OPS/package.opf": strToU8(`<?xml version="1.0"?>
      <package xmlns="http://www.idpf.org/2007/opf" version="3.0" unique-identifier="id">
        <metadata xmlns:dc="http://purl.org/dc/elements/1.1/">
          <dc:identifier id="id">urn:test:book</dc:identifier>
          <dc:title>春天 / Spring</dc:title><dc:creator>测试作者</dc:creator>
        </metadata>
        <manifest>
          <item id="nav" href="nav.xhtml" media-type="application/xhtml+xml" properties="nav"/>
          <item id="c1" href="chapters/one.xhtml" media-type="application/xhtml+xml"/>
          <item id="c2" href="chapters/two.xhtml" media-type="application/xhtml+xml"/>
        </manifest>
        <spine><itemref idref="c1"/><itemref idref="c2"/></spine>
      </package>`),
    "OPS/nav.xhtml": strToU8(`<!doctype html><html xmlns="http://www.w3.org/1999/xhtml"><body>
      <nav epub:type="toc" xmlns:epub="http://www.idpf.org/2007/ops">
        <ol><li><a href="chapters/one.xhtml">第一章</a></li><li><a href="chapters/two.xhtml#top">Chapter Two</a></li></ol>
      </nav></body></html>`),
    "OPS/chapters/one.xhtml": strToU8(`<!doctype html><html xmlns="http://www.w3.org/1999/xhtml"><head>
      <script>window.evil = true</script><style>body{display:none}</style></head><body>
      <h1>错误标题应被目录覆盖</h1><p>这是第一句。This is one sentence.</p>
      <table><tr><td>样本</td><td>均值</td></tr></table>
      <p>E = mc² + ∑ αᵢ。</p><a href="https://evil.test">危险链接</a>
      </body></html>`),
    "OPS/chapters/two.xhtml": strToU8(`<!doctype html><html xmlns="http://www.w3.org/1999/xhtml"><body>
      <h1 id="top">Fallback title</h1><p>Second chapter.</p></body></html>`),
    ...overrides,
  };
  return zipSync(files, { level: 0 }).buffer;
}

test("extracts EPUB metadata, TOC titles, spine order, and safe readable blocks", async () => {
  const book = await parseEpub(makeEpub());

  assert.equal(book.format, "epub");
  assert.equal(book.title, "春天 / Spring");
  assert.equal(book.author, "测试作者");
  assert.deepEqual(book.chapters.map(({ title }) => title), ["第一章", "Chapter Two"]);
  assert.equal(book.chapters[0].blocks.some((block) => block.text.includes("这是第一句")), true);
  assert.equal(book.chapters[0].blocks.some((block) => block.kind === "table" && !block.included), true);
  assert.equal(book.chapters[0].blocks.some((block) => block.kind === "formula" && !block.included), true);
  const combined = book.chapters.flatMap((chapter) => chapter.blocks).map((block) => block.text).join(" ");
  assert.doesNotMatch(combined, /window\.evil|display:none|危险链接|evil\.test/);
});

test("falls back to headings and filenames when a TOC is absent", async () => {
  const book = await parseEpub(makeEpub({ "OPS/nav.xhtml": strToU8("<html><body></body></html>") }));
  assert.deepEqual(book.chapters.map(({ title }) => title), ["错误标题应被目录覆盖", "Fallback title"]);
});

test("rejects encrypted content and unsafe archive paths", async () => {
  await assert.rejects(
    parseEpub(makeEpub({
      "META-INF/encryption.xml": strToU8(`<encryption xmlns="urn:oasis:names:tc:opendocument:xmlns:container"><EncryptedData><EncryptionMethod Algorithm="http://example.test/drm"/><CipherData><CipherReference URI="OPS/chapters/one.xhtml"/></CipherData></EncryptedData></encryption>`),
    })),
    (error) => error.code === "drm_or_encrypted",
  );

  await assert.rejects(
    parseEpub(makeEpub({ "../outside.xhtml": strToU8("bad") })),
    (error) => error.code === "unsafe_archive_path",
  );
});

test("rejects malformed EPUBs with no package or readable spine", async () => {
  await assert.rejects(
    parseEpub(zipSync({ mimetype: strToU8("application/epub+zip") }).buffer),
    (error) => error.code === "invalid_epub",
  );
  await assert.rejects(
    parseEpub(makeEpub({
      "OPS/package.opf": strToU8(`<package xmlns="http://www.idpf.org/2007/opf"><metadata/><manifest/><spine/></package>`),
    })),
    (error) => error.code === "no_readable_content",
  );
});
