"""Tests for HTML transformation utilities."""

from litestar_vite.html_transform import (
    _escape_attr,
    _escape_script,
    inject_body_content,
    inject_head_script,
    inject_json_script,
    inject_page_script,
    inject_vite_dev_scripts,
    set_data_attribute,
)


def test_inject_head_script_basic() -> None:
    """Test basic script injection into head."""
    html = """
    <!DOCTYPE html>
    <html>
    <head>
        <title>Test</title>
    </head>
    <body>
        <div id="app"></div>
    </body>
    </html>
    """
    script = "console.log('test');"
    result = inject_head_script(html, script)

    assert "<script>console.log('test');</script>" in result
    assert result.index("</head>") > result.index("<script>console.log('test');</script>")


def test_inject_head_script_with_nonce() -> None:
    html = "<html><head></head><body></body></html>"
    script = "console.log('nonce');"
    result = inject_head_script(html, script, nonce="abc123")

    assert "<script nonce=\"abc123\">console.log('nonce');</script>" in result


def test_inject_head_script_with_escape() -> None:
    """Test script injection with escaping.</test>"""
    html = "<html><head></head><body></body></html>"
    script = "const x = '</script>';"
    result = inject_head_script(html, script, escape=True)

    # Should escape the closing script tag
    assert r"<\/script>" in result
    assert "</script></script>" not in result


def test_inject_head_script_no_escape() -> None:
    """Test script injection without escaping."""
    html = "<html><head></head><body></body></html>"
    script = "const x = 1;"
    result = inject_head_script(html, script, escape=False)

    assert "<script>const x = 1;</script>" in result


def test_inject_head_script_case_insensitive() -> None:
    """Test script injection with different case head tags."""
    html = "<HTML><HEAD></HEAD><BODY></BODY></HTML>"
    script = "console.log('test');"
    result = inject_head_script(html, script)

    assert "<script>console.log('test');</script>" in result


def test_inject_head_script_with_comment() -> None:
    """Test script injection with HTML comments.

    NOTE: The current regex-based implementation will inject before
    the FIRST occurrence of </head>, even if it's in a comment.
    This is a known limitation - for production HTML generated by
    build tools like Vite, comments with closing tags are rare.
    """
    html = """
    <html>
    <head>
        <title>Test</title>
        <!-- </head> this is a comment -->
    </head>
    <body></body>
    </html>
    """
    script = "console.log('test');"
    result = inject_head_script(html, script)

    # The script is injected (though possibly before the comment)
    assert "<script>console.log('test');</script>" in result
    # For real-world use cases, avoid comments with closing tags


def test_inject_head_script_no_head_tag() -> None:
    """Test script injection when no head tag exists."""
    html = "<html><body></body></html>"
    script = "console.log('test');"
    result = inject_head_script(html, script)

    # Should fall back to injecting before </html> or at the end
    assert "<script>console.log('test');</script>" in result


def test_inject_head_script_empty() -> None:
    """Test injecting empty script returns unchanged HTML."""
    html = "<html><head></head><body></body></html>"
    result = inject_head_script(html, "")

    assert result == html


def test_inject_body_content_end() -> None:
    """Test injecting content at the end of body."""
    html = "<html><head></head><body><div>existing</div></body></html>"
    content = '<div id="portal"></div>'
    result = inject_body_content(html, content, position="end")

    assert '<div id="portal"></div>' in result
    assert result.index("</body>") > result.index('<div id="portal"></div>')


def test_inject_body_content_start() -> None:
    """Test injecting content at the start of body."""
    html = "<html><head></head><body><div>existing</div></body></html>"
    content = '<div id="top"></div>'
    result = inject_body_content(html, content, position="start")

    assert '<div id="top"></div>' in result
    assert result.index('<div id="top"></div>') < result.index("<div>existing</div>")


def test_inject_body_content_case_insensitive() -> None:
    """Test body content injection is case-insensitive."""
    html = "<HTML><HEAD></HEAD><BODY></BODY></HTML>"
    content = '<div id="test"></div>'
    result = inject_body_content(html, content, position="end")

    assert '<div id="test"></div>' in result


def test_inject_body_content_empty() -> None:
    """Test injecting empty content returns unchanged HTML."""
    html = "<html><head></head><body></body></html>"
    result = inject_body_content(html, "", position="end")

    assert result == html


def test_set_data_attribute_id_selector() -> None:
    """Test setting data attribute with ID selector."""
    html = '<html><body><div id="app">content</div></body></html>'
    result = set_data_attribute(html, "#app", "data-page", '{"foo":"bar"}')

    assert 'data-page="' in result
    assert "&quot;" in result  # JSON should be escaped


def test_set_data_attribute_element_selector() -> None:
    """Test setting data attribute with element selector."""
    html = "<html><body><div>content</div></body></html>"
    result = set_data_attribute(html, "div", "data-test", "value")

    assert 'data-test="value"' in result


def test_set_data_attribute_replace_existing() -> None:
    """Test replacing an existing data attribute."""
    html = '<html><body><div id="app" data-page="old">content</div></body></html>'
    result = set_data_attribute(html, "#app", "data-page", "new")

    assert 'data-page="new"' in result
    assert 'data-page="old"' not in result


def test_set_data_attribute_escapes_value() -> None:
    """Test that attribute values are properly escaped."""
    html = '<html><body><div id="app"></div></body></html>'
    # Value with characters that need escaping
    value = '<script>alert("xss")</script>'
    result = set_data_attribute(html, "#app", "data-value", value)

    # Should be escaped
    assert "&lt;" in result
    assert "&gt;" in result
    assert "&quot;" in result
    # Should not contain unescaped dangerous characters
    assert '<script>alert("xss")</script>' not in result


def test_set_data_attribute_empty_selector() -> None:
    """Test with empty selector returns unchanged HTML."""
    html = '<html><body><div id="app"></div></body></html>'
    result = set_data_attribute(html, "", "data-test", "value")

    assert result == html


def test_inject_json_script() -> None:
    """Test injecting JSON data as a script."""
    html = "<html><head></head><body></body></html>"
    data = {"routes": {"home": "/", "about": "/about"}, "user": {"id": 1, "name": "Test"}}
    result = inject_json_script(html, "__ROUTES__", data)

    assert "window.__ROUTES__" in result
    assert '{"routes":' in result
    assert "home" in result
    assert "/about" in result


def test_inject_json_script_with_unicode() -> None:
    """Test JSON injection preserves unicode characters."""
    html = "<html><head></head><body></body></html>"
    data = {"message": "Hello 世界 🌍"}
    result = inject_json_script(html, "__DATA__", data)

    assert "window.__DATA__" in result
    # Unicode should be preserved (ensure_ascii=False)
    assert "世界" in result
    assert "🌍" in result


def test_complex_html_transformation() -> None:
    """Test multiple transformations on the same HTML."""
    html = """
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Test App</title>
    </head>
    <body>
        <div id="app"></div>
    </body>
    </html>
    """

    # Inject routes metadata
    routes = {"home": "/", "users": "/users"}
    html = inject_json_script(html, "__ROUTES__", routes)

    # Inject head script
    html = inject_head_script(html, "console.log('init');")

    # Set data attribute
    html = set_data_attribute(html, "#app", "data-page", '{"component":"Home"}')

    # Inject body content
    html = inject_body_content(html, '<div id="portal"></div>', position="end")

    # Verify all transformations
    assert "window.__ROUTES__" in html
    assert "console.log('init');" in html
    assert "data-page=" in html
    assert '<div id="portal"></div>' in html


def test_malformed_html_handling() -> None:
    """Test handling of malformed HTML.

    Risk mitigation: ensure transformer doesn't break on malformed HTML.
    """
    # HTML with unclosed tags
    html = "<html><head><body><div id='app'>content"
    script = "console.log('test');"

    # Should not raise an exception
    result = inject_head_script(html, script)
    assert script in result


def test_html_with_inline_scripts() -> None:
    """Test that existing inline scripts are preserved."""
    html = """
    <html>
    <head>
        <script>var existing = 1;</script>
    </head>
    <body></body>
    </html>
    """
    script = "var injected = 2;"
    result = inject_head_script(html, script)

    # Both scripts should be present
    assert "var existing = 1;" in result
    assert "var injected = 2;" in result


def test_escape_script_helper() -> None:
    """Test the internal script escaping helper."""
    script = "const html = '</script><script>alert(1)</script>';"
    escaped = _escape_script(script)

    assert r"<\/script>" in escaped
    assert "</script>" not in escaped or escaped.count("</script>") == 0


def test_escape_attr_helper() -> None:
    """Test the internal attribute escaping helper."""
    value = '<script>alert("xss")&test</script>'
    escaped = _escape_attr(value)

    assert "&lt;" in escaped
    assert "&gt;" in escaped
    assert "&quot;" in escaped
    assert "&amp;" in escaped
    assert "<script>" not in escaped


def test_inject_page_script_basic() -> None:
    """Test basic page script injection before </body>."""
    html = """
    <!DOCTYPE html>
    <html>
    <head><title>Test</title></head>
    <body>
        <div id="app"></div>
    </body>
    </html>
    """
    json_data = '{"component":"Home","props":{}}'
    result = inject_page_script(html, json_data)

    assert '<script type="application/json" id="app_page">' in result
    assert '{"component":"Home","props":{}}' in result
    assert result.index("</script>") < result.index("</body>")


def test_inject_page_script_with_nonce() -> None:
    """Test page script injection with CSP nonce."""
    html = "<html><head></head><body></body></html>"
    json_data = '{"component":"Test"}'
    result = inject_page_script(html, json_data, nonce="abc123")

    assert '<script type="application/json" id="app_page" nonce="abc123">' in result


def test_inject_page_script_escapes_closing_tags() -> None:
    """Test that </script> sequences in JSON are escaped to prevent XSS."""
    html = "<html><head></head><body></body></html>"
    # Malicious JSON that tries to break out of script tag
    json_data = '{"content":"</script><script>alert(1)</script>"}'
    result = inject_page_script(html, json_data)

    # Should escape </ to <\/ to prevent premature tag closure
    assert r"<\/script>" in result
    # Should NOT have unescaped </script> in the JSON content
    # (other than the closing tag of our script element)
    assert result.count("</script>") == 1  # Only the legitimate closing tag


def test_inject_page_script_empty_json() -> None:
    """Test that empty JSON returns unchanged HTML."""
    html = "<html><head></head><body></body></html>"
    result = inject_page_script(html, "")

    assert result == html


def test_inject_page_script_no_body() -> None:
    """Test fallback when no </body> tag exists."""
    html = "<html><head></head></html>"
    json_data = '{"component":"Test"}'
    result = inject_page_script(html, json_data)

    # Should still inject the script (appended at end)
    assert '<script type="application/json" id="app_page">' in result


def test_inject_page_script_custom_id() -> None:
    """Test page script with custom ID."""
    html = "<html><head></head><body></body></html>"
    json_data = '{"data":"test"}'
    result = inject_page_script(html, json_data, script_id="custom_page")

    assert '<script type="application/json" id="custom_page">' in result


def test_inject_page_script_large_payload() -> None:
    """Test script injection with large JSON payload."""
    html = "<html><head></head><body></body></html>"
    # Create a large JSON payload
    large_data = '{"items":' + str(list(range(1000))) + "}"
    result = inject_page_script(html, large_data)

    assert '<script type="application/json" id="app_page">' in result
    assert "[0, 1, 2, 3" in result


def test_inject_page_script_vs_data_attribute_escaping() -> None:
    """Test that script element avoids the heavy escaping needed for data attributes.

    This is the key performance benefit - script elements don't need HTML entity escaping.
    """
    html_with_script = "<html><body></body></html>"
    html_with_attr = '<html><body><div id="app"></div></body></html>'
    json_data = '{"message":"Hello <World> & \\"Friends\\""}'

    # Script element: JSON is mostly unchanged (only </ escaping)
    script_result = inject_page_script(html_with_script, json_data)

    # Data attribute: requires full HTML entity escaping
    attr_result = set_data_attribute(html_with_attr, "#app", "data-page", json_data)

    # Script element preserves quotes and angle brackets
    assert '"Hello <World>' in script_result or r'"Hello <World>' in script_result
    assert "&quot;" not in script_result
    assert "&lt;" not in script_result
    assert "&gt;" not in script_result

    # Data attribute must escape everything
    assert "&quot;" in attr_result
    assert "&lt;" in attr_result
    assert "&gt;" in attr_result


def test_inject_vite_dev_scripts_basic() -> None:
    """Test basic Vite dev scripts injection."""
    html = """
    <!DOCTYPE html>
    <html>
    <head><title>Test</title></head>
    <body><div id="app"></div></body>
    </html>
    """
    result = inject_vite_dev_scripts(html, "http://localhost:5173", asset_url="/static/")

    assert '<script type="module" src="/static/@vite/client"></script>' in result


def test_inject_vite_dev_scripts_with_react() -> None:
    """Test Vite dev scripts with React Fast Refresh preamble."""
    html = "<html><head></head><body></body></html>"
    result = inject_vite_dev_scripts(html, "", asset_url="/static/", is_react=True)

    assert "RefreshRuntime" in result
    assert "@react-refresh" in result
    assert "__vite_plugin_react_preamble_installed__" in result


def test_inject_vite_dev_scripts_with_nonce() -> None:
    """Test Vite dev scripts with CSP nonce."""
    html = "<html><head></head><body></body></html>"
    result = inject_vite_dev_scripts(html, "", asset_url="/static/", csp_nonce="abc123")

    assert 'nonce="abc123"' in result


def test_inject_vite_dev_scripts_transforms_entry_point_urls() -> None:
    """Test that entry point script URLs are transformed when resource_dir is provided."""
    html = """
    <!DOCTYPE html>
    <html>
    <head><title>Test</title></head>
    <body>
        <div id="app"></div>
        <script type="module" src="/resources/main.tsx"></script>
    </body>
    </html>
    """
    result = inject_vite_dev_scripts(html, "http://localhost:5173", asset_url="/static/", resource_dir="resources")

    # Entry point URL should be transformed to include /static/ prefix
    assert 'src="/static/resources/main.tsx"' in result
    # Original URL should not be present
    assert 'src="/resources/main.tsx"' not in result
    # Vite client should also be injected
    assert '<script type="module" src="/static/@vite/client"></script>' in result


def test_inject_vite_dev_scripts_does_not_double_prefix() -> None:
    """Test that already-prefixed URLs are not double-prefixed."""
    html = """
    <html>
    <head></head>
    <body>
        <script type="module" src="/static/resources/main.tsx"></script>
    </body>
    </html>
    """
    result = inject_vite_dev_scripts(html, "", asset_url="/static/", resource_dir="resources")

    # Should NOT have double prefix
    assert "/static/static/" not in result
    # Original prefixed URL should be preserved
    assert 'src="/static/resources/main.tsx"' in result


def test_inject_vite_dev_scripts_no_resource_dir() -> None:
    """Test that entry point URLs are not transformed without resource_dir."""
    html = """
    <html>
    <head></head>
    <body>
        <script type="module" src="/resources/main.tsx"></script>
    </body>
    </html>
    """
    result = inject_vite_dev_scripts(html, "", asset_url="/static/", resource_dir=None)

    # Without resource_dir, the URL should remain unchanged
    assert 'src="/resources/main.tsx"' in result


def test_inject_vite_dev_scripts_multiple_entry_points() -> None:
    """Test transformation of multiple entry point scripts."""
    html = """
    <html>
    <head></head>
    <body>
        <script type="module" src="/resources/main.tsx"></script>
        <script type="module" src="/resources/vendor.ts"></script>
        <script src="/other/external.js"></script>
    </body>
    </html>
    """
    result = inject_vite_dev_scripts(html, "", asset_url="/static/", resource_dir="resources")

    # Both resources/ scripts should be transformed
    assert 'src="/static/resources/main.tsx"' in result
    assert 'src="/static/resources/vendor.ts"' in result
    # Non-matching script should remain unchanged
    assert 'src="/other/external.js"' in result


def test_inject_vite_dev_scripts_src_subdir() -> None:
    """Test with 'src' as resource_dir."""
    html = """
    <html>
    <head></head>
    <body>
        <script type="module" src="/src/main.tsx"></script>
    </body>
    </html>
    """
    result = inject_vite_dev_scripts(html, "", asset_url="/static/", resource_dir="src")

    assert 'src="/static/src/main.tsx"' in result
