"""Regression guard for the edit-chrome-injection bug.

Bug: On the unified staging review, clicking Edit → "Done Editing" on a staged
assignment/page/discussion (even with zero edits) injected the preview chrome
("← Back to Course Review", the item title) into the saved staged file. The
re-render step scraped the per-item preview page's `.user_content` div, which —
because `_wrap_html` PREPENDED the preview-top-bar inside that div — captured the
chrome and wrote it back to disk on the next save cycle.

Two fixes:
  1A (JS): `stopEditing` now re-renders from the /raw API
     (/api/staging/{c}/{type}/{id}/raw), which returns the clean staged HTML
     with no chrome. (JS can't be unit-tested from Python; it is covered by the
     structural guarantee below — chrome lives OUTSIDE .user_content, so even a
     legacy .user_content scrape would no longer capture it.)
  1B (structural): `_wrap_html` injects the preview-top-bar into a {{TOP_BAR}}
     slot OUTSIDE the .show-content/.user_content div. These tests assert that
     structural guarantee plus the /raw verbatim-passthrough regression guard.
"""

import io
import json
import pathlib
import re
import sys

import pytest

SCRIPTS_DIR = pathlib.Path(__file__).resolve().parents[1] / "scripts"
sys.path.insert(0, str(SCRIPTS_DIR))

import staging_server  # noqa: E402


VALID_ORIGIN = "http://localhost:8111"


# ── Fix 1B: preview-top-bar lives outside .user_content ──────────────────

def _top_bar_index(wrapped):
    return wrapped.find('class="preview-top-bar"')


def _user_content_open_index(wrapped):
    m = re.search(r'<div class="show-content user_content"', wrapped)
    return m.start() if m else -1


def test_top_bar_rendered_before_user_content_div():
    content = "<h2>Real Staged Heading</h2><p>Real body.</p>"
    wrapped = staging_server._wrap_html(content, title="m1 Homework", course_id="256162")

    top_idx = _top_bar_index(wrapped)
    uc_idx = _user_content_open_index(wrapped)

    assert top_idx != -1, "preview-top-bar should be present when course_id is set"
    assert uc_idx != -1, "show-content user_content div should be present"
    # The top bar must appear BEFORE the .user_content div opens.
    assert top_idx < uc_idx


def test_top_bar_not_inside_user_content():
    content = "<h2>Real Staged Heading</h2>"
    wrapped = staging_server._wrap_html(content, title="m1 Homework", course_id="256162")

    uc_open = _user_content_open_index(wrapped)
    # Find the close of the .user_content div by locating the contentMain close.
    # Simpler: the substring from the .user_content open onward must not contain
    # the preview-top-bar markup.
    tail = wrapped[uc_open:]
    assert 'class="preview-top-bar"' not in tail, (
        "chrome must not appear inside or after the .user_content div opening"
    )


def test_user_content_holds_only_the_staged_content_never_chrome():
    """Staged content occupies .user_content alone; chrome stays outside it.

    The body is no longer inlined -- it renders inside a sandboxed iframe (see
    tests/test_staging_preview_sandbox.py), so .user_content holds exactly the
    sandboxed fragment for that body and nothing else. The property this test
    exists to protect is unchanged: no preview chrome may appear inside it.
    """
    content = "<h2>Categorical Predictors</h2><p>No chrome here.</p>"
    wrapped = staging_server._wrap_html(content, title="Module 3", course_id="256162")

    # Extract the inner HTML of the .user_content div.
    m = re.search(
        r'<div class="show-content user_content">(.*?)</div>\s*</div>\s*</body>',
        wrapped,
        re.DOTALL,
    )
    assert m, "could not locate .user_content inner block"
    inner = m.group(1).strip()

    assert inner == staging_server._sandboxed_fragment(content, "256162")
    # The staged body still renders -- escaped into the iframe's srcdoc.
    assert "Categorical Predictors" in inner
    assert "Back to Course Review" not in inner
    assert "preview-top-bar" not in inner


def test_empty_course_id_leaves_no_top_bar_placeholder():
    content = "<p>Standalone content.</p>"
    wrapped = staging_server._wrap_html(content, title="X", course_id="")

    assert "{{TOP_BAR}}" not in wrapped, "TOP_BAR placeholder must be replaced"
    assert "{{CONTENT}}" not in wrapped
    assert 'class="preview-top-bar"' not in wrapped, (
        "no chrome should render when course_id is empty"
    )
    assert staging_server._sandboxed_fragment(content, "") in wrapped


def test_no_template_placeholders_leak_with_course_id():
    wrapped = staging_server._wrap_html("<p>ok</p>", title="T", course_id="123")
    assert "{{TOP_BAR}}" not in wrapped
    assert "{{CONTENT}}" not in wrapped
    assert "{{TITLE}}" not in wrapped


# ── Fix 1A support: /raw returns the staged file verbatim ────────────────


@pytest.fixture()
def courses_root(tmp_path, monkeypatch):
    monkeypatch.setattr(staging_server, "COURSES_DIR", tmp_path)
    return tmp_path


class _MockServer:
    def __init__(self, port=8111):
        self.server_address = ("127.0.0.1", port)


class HandlerHarness(staging_server.StagingHandler):
    def __init__(self, method, path, body=None, headers=None):
        payload = b""
        req_headers = dict(headers or {})
        req_headers.setdefault("Host", "127.0.0.1:8111")
        if body is not None:
            payload = json.dumps(body).encode("utf-8")
            req_headers.setdefault("Content-Type", "application/json")
            req_headers.setdefault("Content-Length", str(len(payload)))
        self.command = method
        self.path = path
        self.headers = req_headers
        self.rfile = io.BytesIO(payload)
        self.wfile = io.BytesIO()
        self.server = _MockServer()
        self.status = None
        self.response_headers = []

    def send_response(self, code, message=None):
        self.status = code

    def send_header(self, keyword, value):
        self.response_headers.append((keyword, value))

    def end_headers(self):
        pass


def request(method, path, body=None, headers=None):
    handler = HandlerHarness(method, path, body=body, headers=headers)
    getattr(handler, f"do_{method}")()
    return handler.status, handler.wfile.getvalue()


def write_staged(root, course_id="course1", obj_type="assignments",
                 obj_id="m1-homework", content="<h2>HW</h2><p>body</p>"):
    path = root / course_id / "staging" / obj_type / f"{obj_id}{staging_server.EXTENSIONS[obj_type]}"
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(content, encoding="utf-8")
    return path


def test_raw_endpoint_returns_staged_content_verbatim(courses_root):
    raw_content = "<h2>Homework: Alignment Review</h2><p>Original staged body — no chrome.</p>"
    write_staged(courses_root, content=raw_content)

    status, body = request("GET", "/api/staging/course1/assignments/m1-homework/raw")

    assert status == 200
    payload = json.loads(body)
    assert payload["ok"] is True
    assert payload["data"] == raw_content
    # The verbatim staged data must never contain preview chrome.
    assert "Back to Course Review" not in payload["data"]
    assert "preview-top-bar" not in payload["data"]
    assert "mtime" in payload
