"""Tests for ieum.server.sanitize -- HTML sanitization.

Covers:
- Allowed tags pass through unchanged
- Dangerous tags (script, iframe, object, embed) are stripped
- Event handler attributes (onclick, onerror, onload) are removed
- javascript: URLs in href and src are blocked
- Normal markdown HTML is preserved
- XSS attack payloads are neutralized
- Empty input handling
"""

import pytest

from ieum.server.sanitize import (
    ALLOWED_TAGS,
    DANGEROUS_ATTR_PATTERN,
    sanitize_agent_output,
    sanitize_html,
)


# ---------------------------------------------------------------------------
# Allowed HTML passes through
# ---------------------------------------------------------------------------


class TestAllowedTags:
    """Test that allowed HTML tags are preserved."""

    @pytest.mark.parametrize(
        "html,expected_tag",
        [
            ("<p>paragraph</p>", "<p>"),
            ("<strong>bold</strong>", "<strong>"),
            ("<em>italic</em>", "<em>"),
            ("<code>inline code</code>", "<code>"),
            ("<pre>preformatted</pre>", "<pre>"),
            ("<h1>heading</h1>", "<h1>"),
            ("<h2>heading</h2>", "<h2>"),
            ("<ul><li>item</li></ul>", "<li>"),
            ("<ol><li>item</li></ol>", "<ol>"),
            ("<blockquote>quote</blockquote>", "<blockquote>"),
            ("<br/>", "<br"),
            ("<hr/>", "<hr"),
            ("<table><tr><td>cell</td></tr></table>", "<table>"),
            ("<a href=\"https://example.com\">link</a>", "<a"),
            ("<img src=\"image.png\" alt=\"test\"/>", "<img"),
        ],
    )
    def test_allowed_tags_preserved(self, html, expected_tag):
        result = sanitize_html(html)
        assert expected_tag in result

    def test_allowed_attributes_preserved(self):
        html = '<a href="https://example.com" title="Example">link</a>'
        result = sanitize_html(html)
        assert 'href="https://example.com"' in result
        assert 'title="Example"' in result

    def test_img_attributes_preserved(self):
        html = '<img src="photo.jpg" alt="Photo" width="100" height="50"/>'
        result = sanitize_html(html)
        assert 'src="photo.jpg"' in result
        assert 'alt="Photo"' in result


# ---------------------------------------------------------------------------
# Dangerous tags stripped
# ---------------------------------------------------------------------------


class TestDangerousTags:
    """Test that dangerous HTML tags are completely removed."""

    @pytest.mark.parametrize(
        "tag",
        [
            "script",
            "iframe",
            "object",
            "embed",
            "form",
            "input",
            "button",
            "select",
            "textarea",
            "style",
            "link",
            "meta",
            "base",
            "applet",
        ],
    )
    def test_dangerous_tag_stripped(self, tag):
        html = f"<{tag}>dangerous content</{tag}>"
        result = sanitize_html(html)
        assert f"<{tag}" not in result.lower()

    def test_script_with_src_stripped(self):
        html = '<script src="evil.js"></script>'
        result = sanitize_html(html)
        assert "<script" not in result.lower()

    def test_nested_dangerous_tags(self):
        html = '<div><script>alert("xss")</script><p>safe</p></div>'
        result = sanitize_html(html)
        assert "<script" not in result.lower()
        assert "<p>safe</p>" in result


# ---------------------------------------------------------------------------
# Event handler attributes removed
# ---------------------------------------------------------------------------


class TestEventHandlers:
    """Test that event handler attributes are stripped."""

    @pytest.mark.parametrize(
        "attr",
        [
            "onclick",
            "onerror",
            "onload",
            "onmouseover",
            "onfocus",
            "onblur",
            "onsubmit",
        ],
    )
    def test_event_handler_removed(self, attr):
        html = f'<div {attr}="alert(1)">content</div>'
        result = sanitize_html(html)
        assert attr not in result.lower()

    def test_style_attribute_removed(self):
        html = '<p style="background:url(javascript:alert(1))">text</p>'
        result = sanitize_html(html)
        assert "style=" not in result.lower()


# ---------------------------------------------------------------------------
# javascript: URLs blocked
# ---------------------------------------------------------------------------


class TestJavascriptUrls:
    """Test that javascript: protocol URLs are neutralized."""

    def test_javascript_href_blocked(self):
        html = '<a href="javascript:alert(1)">click me</a>'
        result = sanitize_html(html)
        assert "javascript:" not in result.lower()

    def test_vbscript_href_blocked(self):
        html = '<a href="vbscript:MsgBox(1)">click</a>'
        result = sanitize_html(html)
        assert "vbscript:" not in result.lower()

    def test_data_uri_blocked(self):
        """data: URIs (non-image) should not produce a working href.

        The sanitizer may HTML-escape the attribute value rather than remove
        the entire tag. Either way, the result must not contain an unescaped
        data: protocol that a browser could navigate to.
        """
        html = '<a href="data:text/html,<script>alert(1)</script>">click</a>'
        result = sanitize_html(html)
        # The href should either be removed entirely or the value should be
        # escaped so the browser cannot navigate to it.
        # Check that there is no clean href="data:..." (unescaped)
        assert 'href="data:' not in result.lower()

    def test_safe_href_preserved(self):
        html = '<a href="https://example.com">safe link</a>'
        result = sanitize_html(html)
        assert 'href="https://example.com"' in result


# ---------------------------------------------------------------------------
# XSS payloads
# ---------------------------------------------------------------------------


class TestXssPayloads:
    """Test common XSS attack payloads are neutralized."""

    @pytest.mark.parametrize(
        "payload",
        [
            '<script>alert("XSS")</script>',
            '<img src=x onerror=alert(1)>',
            '<svg onload=alert(1)>',
            '<body onload=alert(1)>',
            '<iframe src="javascript:alert(1)">',
            '<a href="javascript:alert(document.cookie)">click</a>',
            '"><script>alert(1)</script>',
            "';alert(String.fromCharCode(88,83,83))//",
            '<IMG SRC="javascript:alert(\'XSS\');">',
        ],
    )
    def test_xss_payload_neutralized(self, payload):
        result = sanitize_html(payload)
        assert "<script" not in result.lower()
        assert "javascript:" not in result.lower()
        # Event handlers should be removed
        assert "onerror=" not in result.lower()
        assert "onload=" not in result.lower()


# ---------------------------------------------------------------------------
# Edge cases
# ---------------------------------------------------------------------------


class TestEdgeCases:
    """Test edge cases in sanitization."""

    def test_empty_string(self):
        assert sanitize_html("") == ""

    def test_none_returns_none(self):
        # sanitize_html returns falsy input as-is
        assert sanitize_html("") == ""

    def test_plain_text_unchanged(self):
        text = "This is plain text with no HTML."
        assert sanitize_html(text) == text

    def test_entities_preserved(self):
        html = "<p>&amp; &lt; &gt;</p>"
        result = sanitize_html(html)
        assert "&amp;" in result

    def test_rel_noopener_added_for_target(self):
        html = '<a href="https://example.com" target="_blank">link</a>'
        result = sanitize_html(html)
        assert "noopener" in result


# ---------------------------------------------------------------------------
# sanitize_agent_output wrapper
# ---------------------------------------------------------------------------


class TestSanitizeAgentOutput:
    """Test the main entry point."""

    def test_plain_text_passes_through(self):
        text = "Hello, this is just text."
        assert sanitize_agent_output(text) == text

    def test_html_is_sanitized(self):
        text = '<p>Safe</p><script>alert(1)</script>'
        result = sanitize_agent_output(text)
        assert "<script" not in result
        assert "<p>Safe</p>" in result

    def test_empty_returns_empty(self):
        assert sanitize_agent_output("") == ""
