"""Pin what canvas.asu.edu actually does to page bodies on write/read.

Each test writes one "tricky" body to a live page via
canvas_api.update_page(..., from_staging=True) and reads it back, asserting
the documented/expected behavior. Uses update_page directly rather than the
staged push_cycle (that mechanic is covered by test_live_push_cycle.py) per
docs/plan-live-integration-tests.md.

Confidence note: the HTML-comment, <script>, and same-domain-iframe cases
pin behaviors already documented and relied on elsewhere in this codebase
(CLAUDE.md's MUST-2 guardrail, the iframe-only interactives rule), so they
assert a single hard expectation. The inline-style cases are LIVE-VERIFIED
(reviewer probes, 2026-07-10, canvas.asu.edu course 260866) with one
exception the first live run discovered: Canvas's sanitizer STRIPS
``font-weight`` from inline styles — any value (bold/400/600/700), on
p/span/div/td/h3 alike — while sibling properties (color, text-align,
background-color, padding, border-left) survive, and a style attribute
Canvas does not touch round-trips byte-identical. Inline styles are
therefore NOT preserved wholesale; see
test_font_weight_stripped_from_inline_style for the pinned quirk.
The HTML-entity/quote/dash cases in TestHtmlEntities are also LIVE-VERIFIED
(reviewer probe, 2026-07-10, course 260866, create_page + get_page
round-trip): the body comes back byte-identical — written entities stay
entities (``&amp;``, ``&lt;``), and curly quotes / em dash written as
literal Unicode stay literal Unicode (Canvas does not entity-encode them).
Each assertion pins that exact serialization.
"""

import os
import sys

import pytest

sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "scripts"))

import canvas_api  # noqa: E402


@pytest.fixture()
def roundtrip_page(page_factory):
    """One live page reused by every case in this module."""
    slug, _ = page_factory("html-roundtrip", "<p>seed</p>")
    return slug


def _set_and_fetch(live_config, wait_for, slug, body, marker):
    """Write `body`, then poll get_page until `marker` (a stable ASCII tag
    with no special characters) shows up in the live body.

    Polling on plain object existence isn't enough here — get_page always
    returns a truthy dict even when the body hasn't propagated yet — so we
    poll on the actual content via a marker we're certain survives
    unchanged, and only then inspect whatever happened to the tricky part
    of the body under test.
    """
    ok = canvas_api.update_page(live_config, slug, body, from_staging=True)
    assert ok, f"update_page failed for slug={slug}"

    def _fetch_when_ready():
        page = canvas_api.get_page(live_config, slug)
        if page and marker in (page.get("body") or ""):
            return page
        return None

    page = wait_for(_fetch_when_ready)
    assert page is not None, (
        f"get_page never showed marker {marker!r} after update for slug={slug}"
    )
    return page.get("body") or ""


class TestHtmlEntities:
    """Live-verified 2026-07-10 (reviewer probe, course 260866): entity/
    Unicode serialization round-trips byte-identical — see module docstring.
    """

    def test_ampersand_entity_survives(self, live_config, wait_for, roundtrip_page):
        # VERIFIED live: a written &amp; entity stays an entity.
        body = "<p>ITEST-AMP: Fish &amp; Chips</p>"
        returned = _set_and_fetch(live_config, wait_for, roundtrip_page, body, "ITEST-AMP")
        assert "Fish &amp; Chips" in returned, (
            f"Expected '&amp;' to survive as an entity byte-identically; got: {returned!r}"
        )

    def test_lt_entity_survives(self, live_config, wait_for, roundtrip_page):
        # VERIFIED live: a written &lt; entity stays an entity.
        body = "<p>ITEST-LT: 3 &lt; 5</p>"
        returned = _set_and_fetch(live_config, wait_for, roundtrip_page, body, "ITEST-LT")
        assert "3 &lt; 5" in returned, (
            f"Expected '&lt;' to survive as an entity byte-identically; got: {returned!r}"
        )

    def test_curly_quotes_survive(self, live_config, wait_for, roundtrip_page):
        # VERIFIED live: literal Unicode curly quotes stay literal Unicode
        # (Canvas does not entity-encode them).
        body = "<p>ITEST-QUOTES: “Hello” and it’s fine</p>"
        returned = _set_and_fetch(live_config, wait_for, roundtrip_page, body, "ITEST-QUOTES")
        assert "“Hello”" in returned, (
            f"Expected literal Unicode curly double quotes to survive; got: {returned!r}"
        )
        assert "it’s" in returned, (
            f"Expected literal Unicode curly apostrophe to survive; got: {returned!r}"
        )

    def test_em_dash_survives(self, live_config, wait_for, roundtrip_page):
        # VERIFIED live: a literal U+2014 em dash stays literal Unicode
        # (Canvas does not entity-encode it).
        body = "<p>ITEST-DASH: before — after</p>"
        returned = _set_and_fetch(live_config, wait_for, roundtrip_page, body, "ITEST-DASH")
        assert "—" in returned, (
            f"Expected literal U+2014 em dash to survive; got: {returned!r}"
        )


def test_canvas_wrapped_comment_stripped(live_config, wait_for, roundtrip_page):
    """Pin CLAUDE.md's MUST-2 guardrail: Canvas strips the
    ``<!-- canvas-wrapped -->`` marker on save. This is a previously
    documented, already-relied-on behavior (repush_transform.py and the
    MUST-2 guardrail both depend on it), not a fresh guess.
    """
    body = "<!-- canvas-wrapped -->\n<h2>ITEST-COMMENT</h2><p>body</p>"
    returned = _set_and_fetch(live_config, wait_for, roundtrip_page, body, "ITEST-COMMENT")
    assert "canvas-wrapped" not in returned, (
        f"Expected Canvas to strip the canvas-wrapped marker; got: {returned!r}"
    )


def test_script_tag_stripped(live_config, wait_for, roundtrip_page):
    """Pin the iframe-only-interactives foundation: Canvas strips or
    neutralizes <script> in page bodies (CLAUDE.md's "Canvas strips
    <script> blocks" rule for why interactives must deploy via iframe).
    """
    body = "<p>ITEST-SCRIPT</p><script>alert(1)</script>"
    returned = _set_and_fetch(live_config, wait_for, roundtrip_page, body, "ITEST-SCRIPT")
    assert "<script>" not in returned, (
        f"Expected <script> to be stripped/neutralized; got: {returned!r}"
    )
    assert "alert(1)" not in returned, (
        f"Expected script content to be removed, not just unwrapped; got: {returned!r}"
    )


def test_inline_style_preserved(live_config, wait_for, roundtrip_page):
    """Pin the Page Design System's foundation (standards/page-design.md):
    an inline style="" attribute Canvas's sanitizer does not object to
    survives a Canvas write/read round trip byte-identical — every
    generated page relies on this. Verified live 2026-07-10 on
    canvas.asu.edu (course 260866): untouched style attributes are NOT
    re-serialized. Deliberately excludes font-weight, which Canvas strips
    (see test_font_weight_stripped_from_inline_style).
    """
    body = (
        '<p style="color:#8C1D40;text-align:center;background-color:#FFC627;'
        'padding:10px;">ITEST-STYLE</p>'
    )
    returned = _set_and_fetch(live_config, wait_for, roundtrip_page, body, "ITEST-STYLE")
    assert (
        'style="color:#8C1D40;text-align:center;background-color:#FFC627;'
        'padding:10px;"' in returned
    ), (
        f"Expected inline style to be preserved byte-identical; got: {returned!r}"
    )


def test_font_weight_stripped_from_inline_style(live_config, wait_for, roundtrip_page):
    """Named quirk: canvas.asu.edu's sanitizer STRIPS ``font-weight`` from
    inline styles. Verified live 2026-07-10 (reviewer probes, course
    260866): stripped on p/span/div/td/h3 alike, for values bold/400/600/
    700; sibling properties (color, padding, text-align) survive. When
    font-weight was the only property, the whole style attribute was
    removed; when it had siblings, Canvas excised just that declaration
    and re-serialized the survivors with altered whitespace (e.g.
    ``style="color: #8C1D40; "``) — so this test asserts on property
    presence/absence, never on the exact spacing of what survives.
    Practical rule for generators: use <strong>/<b> for bold in Canvas
    page bodies, never inline font-weight.
    """
    body = '<p style="color:#8C1D40;font-weight:bold;">ITEST-FW</p>'
    returned = _set_and_fetch(live_config, wait_for, roundtrip_page, body, "ITEST-FW")
    assert "font-weight" not in returned, (
        f"Expected Canvas to strip font-weight from inline style; got: {returned!r}"
    )
    assert "color:" in returned, (
        "Expected the sibling color declaration to survive font-weight "
        f"excision; got: {returned!r}"
    )


def test_same_domain_iframe_preserved(live_config, wait_for, roundtrip_page):
    """Pin the interactive-deploy pattern (CLAUDE.md): a same-domain
    <iframe src="https://canvas.asu.edu/..."> survives a Canvas write/read
    round trip — this is what makes deploy_interactives.py's iframe-host
    pages work at all.
    """
    src = "https://canvas.asu.edu/courses/260866/files/1/preview"
    body = f'<p>ITEST-IFRAME</p><iframe src="{src}"></iframe>'
    returned = _set_and_fetch(live_config, wait_for, roundtrip_page, body, "ITEST-IFRAME")
    assert src in returned, (
        f"Expected same-domain iframe src to be preserved verbatim; got: {returned!r}"
    )
