"""Regression coverage for issue #1800 file-picker and HTML-open interactions."""

from __future__ import annotations

import re
from pathlib import Path


REPO = Path(__file__).resolve().parents[1]
INDEX_HTML = (REPO / "static" / "index.html").read_text(encoding="utf-8")
BOOT_JS = (REPO / "static" / "boot.js").read_text(encoding="utf-8")
UI_JS = (REPO / "static" / "ui.js").read_text(encoding="utf-8")
STYLE_CSS = (REPO / "static" / "style.css").read_text(encoding="utf-8")
ROUTES_PY = (REPO / "api" / "routes.py").read_text(encoding="utf-8")


def _slice_after(source: str, needle: str, chars: int = 900) -> str:
    idx = source.find(needle)
    assert idx >= 0, f"{needle!r} not found"
    return source[idx : idx + chars]


def _function_body(source: str, name: str) -> str:
    start = source.index(f"def {name}")
    try:
        end = source.index("\n\ndef ", start + 1)
    except ValueError:
        end = len(source)
    return source[start:end]


def test_attach_button_is_non_submit_button():
    """Attach must not act like a submit button in browser/container shells."""
    m = re.search(r"<button[^>]*id=\"btnAttach\"[^>]*>", INDEX_HTML)
    assert m, "btnAttach button not found"
    assert 'type="button"' in m.group(0)


def test_file_input_is_visually_hidden_not_display_none():
    """Hidden file inputs are more consistently opened by user-gesture clicks."""
    m = re.search(r"<input[^>]*id=\"fileInput\"[^>]*>", INDEX_HTML)
    assert m, "fileInput not found"
    tag = m.group(0)
    assert "file-input-visually-hidden" in tag
    assert "display:none" not in tag
    rule = _slice_after(STYLE_CSS, ".file-input-visually-hidden", 240)
    assert "position:absolute" in rule
    assert "opacity:0" in rule


def test_attach_click_prevents_default_and_opens_picker():
    body = _slice_after(BOOT_JS, "$('btnAttach').onclick", 300)
    assert "preventDefault" in body
    assert "$('fileInput').value=''" in body
    assert "$('fileInput').click()" in body


def test_html_chat_attachment_opens_sandboxed_inline_raw_file():
    """Uploaded .html attachments render as an openable link, not an inert badge."""
    body = _slice_after(UI_JS, "function _renderAttachmentHtml", 900)
    assert "_HTML_EXTS.test(fname)" in body
    assert "inline=1" in body
    assert "target=\"_blank\"" in body
    assert "rel=\"noopener\"" in body
    assert "msg-file-badge--html" in body


def test_html_media_open_full_uses_inline_new_tab_not_download():
    """MEDIA: HTML preview's Open full page link should open a browser view."""
    body = _slice_after(UI_JS, "function loadHtmlInline", 1800)
    assert "'&inline=1'" in body
    assert "target=\"_blank\"" in body
    assert "rel=\"noopener\"" in body
    normal_open = next(line for line in body.splitlines() if "html-open-link" in line)
    assert "download=" not in normal_open


def test_media_html_inline_keeps_csp_sandbox():
    """api/media may serve HTML inline only behind a CSP sandbox."""
    # Slice widened to 16000 (was 5000) after the #3234 security work landed a
    # multi-profile-aware deny-list + named-profile-root enumeration +
    # active-workspace carve-out + safety gate earlier in _handle_media, pushing
    # the CSP block to ~12100 chars past the def. (Originally widened 4000→5000
    # for PR #2044's MEDIA_ALLOWED_ROOTS parsing.) The assertion is structural,
    # not positional — generous headroom avoids re-breaking on small future edits.
    body = _slice_after(ROUTES_PY, "def _handle_media", 16000)
    assert 'html_inline_ok = inline_preview and mime == "text/html"' in body
    assert 'csp = "sandbox allow-scripts" if html_inline_ok else None' in body
    assert "csp=csp" in body
    assert "allow-same-origin" not in body


def test_sandboxed_file_responses_do_not_send_x_frame_options():
    """X-Frame-Options: DENY would block the sandbox iframe preview."""
    body = _function_body(ROUTES_PY, "_serve_file_bytes")
    csp_branch = body[body.find("if csp:") : body.find("else:", body.find("if csp:"))]
    assert "Content-Security-Policy" in csp_branch
    assert 'send_header("X-Frame-Options"' not in csp_branch
