# retoor <retoor@molodetz.nl>

from html.parser import HTMLParser

from devplacepy.rendering import render_content, render_title


def test_emoji_shortcodes():
    out = str(render_content("ship it :rocket: :fire:"))
    assert "\U0001F680" in out
    assert "\U0001F525" in out
    assert ":rocket:" not in out


def test_markdown_bold_and_italic():
    out = str(render_content("this is **bold** and _em_"))
    assert "<strong>bold</strong>" in out
    assert "<em>em</em>" in out


def test_youtube_embed():
    out = str(render_content("watch https://youtu.be/dQw4w9WgXcQ now"))
    assert 'class="embed-youtube"' in out
    assert "https://www.youtube.com/embed/dQw4w9WgXcQ" in out
    assert "<iframe" in out


def test_image_embed_marked_for_lightbox():
    out = str(render_content("pic https://example.com/a.png end"))
    assert '<img src="https://example.com/a.png"' in out
    assert "loading=\"lazy\"" in out
    assert "data-lightbox" in out


def test_video_embed():
    out = str(render_content("clip https://example.com/a.mp4"))
    assert '<video src="https://example.com/a.mp4" controls preload="metadata">' in out


def test_audio_embed():
    out = str(render_content("sound https://example.com/a.mp3"))
    assert '<audio src="https://example.com/a.mp3" controls preload="metadata">' in out


def test_plain_url_autolinked():
    out = str(render_content("see https://example.com/page ok"))
    assert '<a href="https://example.com/page" target="_blank" rel="noopener noreferrer">' in out


def test_mention_links_to_profile():
    out = str(render_content("hello @alice_test there"))
    assert '<a href="/profile/alice_test" class="mention-link">@alice_test</a>' in out


def test_email_is_not_a_mention():
    out = str(render_content("mail me at a@b.com please"))
    assert "mention-link" not in out
    assert "a@b.com" in out


def test_url_inside_code_span_is_not_embedded():
    out = str(render_content("use `https://example.com/a.png` inline"))
    assert "<img" not in out
    assert "<code>" in out


def test_fenced_code_keeps_language_class():
    out = str(render_content("```python\nprint(1)\n```"))
    assert 'class="language-python"' in out


def test_raw_html_is_escaped():
    out = str(render_content("<script>alert(1)</script> hi"))
    assert "<script>" not in out
    assert "&lt;script&gt;" in out


def test_javascript_link_is_neutralised():
    out = str(render_content("[click](javascript:alert(1))"))
    assert "javascript:" not in out


def test_render_content_empty():
    assert str(render_content("")) == ""
    assert str(render_content(None)) == ""


def test_title_renders_inline_markdown_and_emoji():
    out = str(render_title("Ship **v2** :rocket:"))
    assert "<strong>v2</strong>" in out
    assert "\U0001F680" in out


def test_title_has_no_block_or_paragraph_tags():
    out = str(render_title("Just a title"))
    assert "<p>" not in out
    assert out.strip() == "Just a title"


def test_title_strips_links_keeping_text():
    out = str(render_title("See [docs](https://example.com) now"))
    assert "<a" not in out
    assert "docs" in out


def test_title_escapes_raw_html():
    out = str(render_title("<script>x</script>Title"))
    assert "<script>" not in out
    assert "&lt;script&gt;" in out


def test_caching_is_idempotent():
    text = "cached **content** :fire:"
    assert str(render_content(text)) == str(render_content(text))


DANGEROUS_SCHEMES = {"javascript", "data", "vbscript", "file"}


class _XSSAudit(HTMLParser):
    def __init__(self) -> None:
        super().__init__(convert_charrefs=True)
        self.violations: list[str] = []

    def handle_starttag(self, tag: str, attrs: list) -> None:
        self._inspect(tag, attrs)

    def handle_startendtag(self, tag: str, attrs: list) -> None:
        self._inspect(tag, attrs)

    def _inspect(self, tag: str, attrs: list) -> None:
        if tag in {"script", "object", "embed"}:
            self.violations.append(f"<{tag}>")
        for name, value in attrs:
            lname = name.lower()
            if lname.startswith("on"):
                self.violations.append(f"{tag}@{name}")
            if lname in {"href", "src", "xlink:href", "formaction"} and value:
                scheme = value.split(":", 1)[0].strip().lower() if ":" in value else ""
                if scheme in DANGEROUS_SCHEMES:
                    self.violations.append(f"{tag}@{name}={value}")


def assert_no_executable_html(rendered: str) -> None:
    audit = _XSSAudit()
    audit.feed(rendered)
    audit.close()
    assert not audit.violations, f"XSS vectors survived rendering: {audit.violations}"


XSS_VECTORS = (
    "<script>alert('xss')</script>",
    "<img src=x onerror=alert('xss')>",
    "<svg onload=alert('xss')>",
    "<svg><script>alert('xss')</script></svg>",
    "<body onload=alert('xss')>",
    "<iframe src=\"javascript:alert('xss')\"></iframe>",
    "<a href=\"javascript:alert('xss')\">click</a>",
    "<div onmouseover=\"alert('xss')\">hover</div>",
    "<input autofocus onfocus=alert('xss')>",
    "<object data=\"javascript:alert('xss')\"></object>",
    "<a href=\"data:text/html,<script>alert('xss')</script>\">x</a>",
    "[click](javascript:alert('xss'))",
    "![img](javascript:alert('xss'))",
    "[click](data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==)",
    "[click](vbscript:msgbox('xss'))",
    "normal text <ScRiPt>alert('xss')</ScRiPt> mixed case",
    "tab\tin\tjava\tscript:alert('xss')",
    "<img src=`x`onerror=alert('xss')>",
    "<a href=\" javascript:alert('xss')\">leading space</a>",
)


def _audit_content_vectors() -> None:
    for vector in XSS_VECTORS:
        assert_no_executable_html(str(render_content(vector)))
        assert_no_executable_html(str(render_content(f"hello {vector} world")))


def _audit_title_vectors() -> None:
    for vector in XSS_VECTORS:
        assert_no_executable_html(str(render_title(vector)))
        assert_no_executable_html(str(render_title(f"title {vector} end")))


def test_xss_post_title_is_neutralised():
    _audit_title_vectors()


def test_xss_post_content_is_neutralised():
    _audit_content_vectors()


def test_xss_comment_content_is_neutralised():
    _audit_content_vectors()


def test_xss_project_title_is_neutralised():
    _audit_title_vectors()


def test_xss_project_description_is_neutralised():
    _audit_content_vectors()


def test_xss_gist_title_is_neutralised():
    _audit_title_vectors()


def test_xss_gist_description_is_neutralised():
    _audit_content_vectors()


def test_xss_news_title_is_neutralised():
    _audit_title_vectors()


def test_xss_news_content_is_neutralised():
    _audit_content_vectors()


def test_xss_message_content_is_neutralised():
    _audit_content_vectors()


def test_xss_poll_question_is_neutralised():
    _audit_title_vectors()


def test_xss_poll_option_label_is_neutralised():
    _audit_title_vectors()


def test_xss_saved_item_title_is_neutralised():
    _audit_title_vectors()


def test_xss_leaderboard_title_is_neutralised():
    _audit_title_vectors()


def test_xss_issue_title_is_neutralised():
    _audit_title_vectors()


def test_xss_issue_body_is_neutralised():
    _audit_content_vectors()


def test_xss_legitimate_youtube_embed_survives_audit():
    out = str(render_content("watch https://youtu.be/dQw4w9WgXcQ now"))
    assert "youtube.com/embed/dQw4w9WgXcQ" in out
    assert_no_executable_html(out)


def test_xss_legitimate_image_embed_survives_audit():
    out = str(render_content("pic https://example.com/a.png end"))
    assert "<img" in out
    assert_no_executable_html(out)


def test_xss_legitimate_link_survives_audit():
    out = str(render_content("see https://example.com/page ok"))
    assert 'href="https://example.com/page"' in out
    assert_no_executable_html(out)
