import { expect, test } from "bun:test";
import { renderDocument, renderDocumentStream, renderStoreHydration } from "../src/index.ts";
import type { StoreContainer } from "@wrnexus/store";

test("store hydration is HTML-safe, bounded, redacted, and JSON-compatible", () => {
  const container = {
    serialize: () => ({
      ProfileStore: {
        display: "</script><script>alert(1)</script>",
        accessToken: "never-render-this",
      },
    }),
  } as unknown as StoreContainer;
  const html = renderStoreHydration(container, 'safe" onload="bad');
  expect(html).not.toContain("</script><script>");
  expect(html).not.toContain("never-render-this");
  expect(html).toContain('nonce="safe&quot; onload=&quot;bad"');
  const payload = html.match(/>(.*)<\/script>$/)?.[1];
  expect(JSON.parse(payload!)).toEqual({
    ProfileStore: { display: "</script><script>alert(1)</script>", accessToken: "[REDACTED]" },
  });

  const oversized = {
    serialize: () => ({ value: "x".repeat(300_000) }),
  } as unknown as StoreContainer;
  expect(() => renderStoreHydration(oversized)).toThrow("exceeds 262144 bytes");
});

test("escapes metadata and script URLs while preserving trusted rendered body", () => {
  const html = renderDocument({
    meta: { title: '</title><script>alert("x")</script>', description: '" onload="x' },
    body: "<main>trusted</main>",
    scripts: ['"><script>alert(1)</script>'],
  });
  expect(html).not.toContain('</title><script>alert("x")</script>');
  expect(html).toContain("&lt;/title&gt;");
  expect(html).toContain("<main>trusted</main>");
  expect(html).not.toContain("<script>alert(1)</script>");
});

test("deduplicates runtime scripts and module preloads", () => {
  const html = renderDocument({
    meta: { title: "Page" },
    body: "",
    scripts: ["/app.js", "/app.js"],
  });
  expect(html.match(/rel="modulepreload"/g)).toHaveLength(1);
  expect(html.match(/src="\/app\.js"/g)).toHaveLength(1);
});

test("always emits a document language and preserves an explicit language", () => {
  const fallback = renderDocument({ meta: {}, body: "" });
  expect(fallback).toContain('<html lang="en">');

  const explicit = renderDocument({
    meta: {},
    body: "",
    htmlAttrs: ' data-theme="dark" lang="fr"',
  });
  expect(explicit).toContain('<html data-theme="dark" lang="fr">');
  expect(explicit).not.toContain('lang="en"');
});

test("merges framework head and scripts into an application document template", () => {
  const html = renderDocument({
    meta: { title: "Document layout" },
    body: "",
    htmlAttrs: ' data-theme="dark"',
    scripts: ["/page.js"],
    documentTemplate:
      '<html data-ui-font="system"><head><meta name="custom" content="yes" /></head><body><div id="app">Page</div></body></html>',
  });
  expect(html).toStartWith("<!doctype html>");
  expect(html).toContain('<html data-ui-font="system" data-theme="dark" lang="en">');
  expect(html).toContain("<title>Document layout</title>");
  expect(html).toContain('<meta name="custom" content="yes" />');
  expect(html).toContain('<script type="module" src="/page.js"></script>');
  expect(html.match(/<html/g)).toHaveLength(1);
  expect(html.match(/<body/g)).toHaveLength(1);
});

test("streams async body chunks inside the document shell", async () => {
  async function* body() {
    yield "<h1>Shell</h1>";
    yield "<p>Later</p>";
  }
  const html = await new Response(
    renderDocumentStream({ meta: { title: "Stream" }, body: body() }),
  ).text();
  expect(html).toContain('<div id="app"><h1>Shell</h1><p>Later</p></div>');
});

test("renders rich script metadata and deduplicates by source", () => {
  const html = renderDocument({
    meta: { title: "Runtime" },
    body: '<main data-wrnexus-runtime="captcha"></main>',
    scripts: [
      {
        src: "/__wrnexus/assets/captcha.abc.js",
        type: "classic",
        defer: true,
        integrity: "sha256-test",
        crossOrigin: "anonymous",
        attributes: { "data-wrnexus-runtime-src": "captcha" },
      },
      { src: "/__wrnexus/assets/captcha.abc.js", type: "classic" },
    ],
  });
  expect(html.match(/captcha\.abc\.js/g)).toHaveLength(1);
  expect(html).toContain("defer");
  expect(html).toContain('integrity="sha256-test"');
  expect(html).toContain('crossorigin="anonymous"');
  expect(html).toContain('data-wrnexus-runtime-src="captcha"');
  expect(html).not.toContain('rel="modulepreload" href="/__wrnexus/assets/captcha.abc.js"');
});

test("promotes WRN style blocks into head after global CSS with CSP nonce", () => {
  const html = renderDocument({
    meta: { title: "Styled" },
    extraHead: '<link rel="stylesheet" href="/global.css" />',
    styleNonce: "nonce-123",
    body:
      '<style data-wrnexus-style="Card" data-wrnexus-style-id="card" data-wrnexus-style-kind="component">.shared{color:blue}</style>' +
      '<style data-wrnexus-style="Page" data-wrnexus-style-id="page" data-wrnexus-style-kind="page">.shared{color:red}</style>' +
      '<style data-wrnexus-style="Layout" data-wrnexus-style-id="layout" data-wrnexus-style-kind="layout">.shared{color:green}</style>' +
      '<main class="shared">Styled</main>',
  });

  expect(html).not.toContain('<div id="app"><style');
  expect(html).toContain('<main class="shared">Styled</main>');
  expect(html.match(/data-wrnexus-style-id=/g)).toHaveLength(3);
  expect(html.match(/nonce="nonce-123"/g)).toHaveLength(3);

  const globalIndex = html.indexOf("/global.css");
  const layoutIndex = html.indexOf('data-wrnexus-style-id="layout"');
  const pageIndex = html.indexOf('data-wrnexus-style-id="page"');
  const componentIndex = html.indexOf('data-wrnexus-style-id="card"');
  expect(globalIndex).toBeLessThan(layoutIndex);
  expect(layoutIndex).toBeLessThan(pageIndex);
  expect(pageIndex).toBeLessThan(componentIndex);
});

test("deduplicates repeated component style blocks", () => {
  const style =
    '<style data-wrnexus-style="Card" data-wrnexus-style-id="card" data-wrnexus-style-kind="component">.card{display:grid}</style>';
  const html = renderDocument({
    meta: { title: "Repeated" },
    body: `${style}<article>One</article>${style}<article>Two</article>`,
  });

  expect(html.match(/data-wrnexus-style-id="card"/g)).toHaveLength(1);
  expect(html).toContain("<article>One</article><article>Two</article>");
});

test("places document-layout WRN styles at the end of head", () => {
  const html = renderDocument({
    meta: { title: "Document styles" },
    styleNonce: "document-nonce",
    body: "",
    documentTemplate:
      '<html><head><link rel="stylesheet" href="/document-global.css" /></head><body>' +
      '<style data-wrnexus-style="Document" data-wrnexus-style-id="document-layout" data-wrnexus-style-kind="layout">.shell{display:block}</style>' +
      '<div id="app" class="shell">Page</div></body></html>',
  });

  const globalIndex = html.indexOf("/document-global.css");
  const styleIndex = html.indexOf('data-wrnexus-style-id="document-layout"');
  const headEnd = html.indexOf("</head>");
  expect(globalIndex).toBeLessThan(styleIndex);
  expect(styleIndex).toBeLessThan(headEnd);
  expect(html).not.toContain("<body><style");
});
