"""Unit tests for the plain-text sanitiser (INDL-44 SEC-02 / SEC-12).

Covers the OWASP XSS Filter Evasion payloads mandated by CIS 16 — every payload
must be reduced to plain text with all HTML tags stripped.
"""
import pytest

from src.core.utils.sanitize import (
    NullByteError,
    sanitize_display_name,
    sanitize_plain_text,
    strip_html,
)

# OWASP XSS Filter Evasion Cheat Sheet — a representative spread of vectors.
XSS_PAYLOADS = [
    "<script>alert(1)</script>",
    "<img src=x onerror=alert(1)>",
    'javascript:alert(1)',
    '"><svg onload=alert(1)>',
    "<style>body{background:url('javascript:alert(1)')}</style>",
    "<iframe src=javascript:alert(1)></iframe>",
    "<a href=\"javascript:alert(1)\">click</a>",
    "<img src=`x`onerror=alert(1)>",
]


@pytest.mark.parametrize("payload", XSS_PAYLOADS)
def test_no_tag_markup_survives(payload):
    """After sanitisation no HTML *element* can be formed.

    A tag requires a `<` — with every `<` removed, any orphan `>` left in the
    text is inert and safely encoded when rendered as a React text node.
    """
    cleaned = sanitize_plain_text(payload)
    assert "<" not in cleaned
    lowered = cleaned.lower()
    assert "<script" not in lowered
    assert "<svg" not in lowered
    assert "<img" not in lowered
    assert "<iframe" not in lowered


def test_script_body_removed():
    assert sanitize_plain_text("<script>alert(1)</script>") == ""


def test_style_body_removed():
    cleaned = sanitize_plain_text("Hello<style>.x{color:red}</style> World")
    assert "color:red" not in cleaned
    assert "Hello" in cleaned and "World" in cleaned


def test_benign_text_preserved_and_entities_decoded():
    assert sanitize_plain_text("She was <b>wonderful</b> &amp; kind.") == "She was wonderful & kind."


def test_javascript_scheme_kept_as_inert_text():
    # No tag, so it stays as literal text — safe when rendered as a React text node.
    assert sanitize_plain_text("javascript:alert(1)") == "javascript:alert(1)"


def test_null_byte_rejected():
    with pytest.raises(NullByteError):
        sanitize_plain_text("bad\x00byte")


def test_control_characters_stripped():
    cleaned = sanitize_plain_text("line\x07bell\x1bescape")
    assert "\x07" not in cleaned and "\x1b" not in cleaned


def test_max_length_enforced():
    with pytest.raises(ValueError):
        sanitize_plain_text("a" * 10, max_length=5)


def test_whitespace_collapsed_and_trimmed():
    assert sanitize_plain_text("   too    many     spaces   ") == "too many spaces"


def test_strip_html_direct():
    assert strip_html("<p>Hi <em>there</em></p>") == "Hi there"


def test_sanitize_display_name_strips_markup_and_truncates():
    assert sanitize_display_name("<b>Patricia O'Brien</b>") == "Patricia O'Brien"
    assert sanitize_display_name(None) is None
    assert sanitize_display_name("") is None
    assert len(sanitize_display_name("x" * 500)) == 200
