"""
HTML Sanitization Utilities for ThrillWiki.

This module provides functions for sanitizing user-generated HTML content
to prevent XSS (Cross-Site Scripting) attacks while allowing safe HTML
formatting.

Security Note:
    Always sanitize user-generated content before rendering with |safe filter
    or mark_safe(). Never trust user input.

Usage:
    from apps.core.utils.html_sanitizer import sanitize_html, sanitize_for_json

    # In views
    context['description'] = sanitize_html(user_input)

    # For JSON/JavaScript contexts
    json_safe = sanitize_for_json(data)
"""

import json
import re
from html import escape as html_escape
from typing import Any

try:
    import bleach

    BLEACH_AVAILABLE = True
except ImportError:
    BLEACH_AVAILABLE = False


# =============================================================================
# Allowed HTML Configuration
# =============================================================================

# Default allowed HTML tags for user-generated content
ALLOWED_TAGS = frozenset(
    [
        # Text formatting
        "p",
        "br",
        "hr",
        "strong",
        "b",
        "em",
        "i",
        "u",
        "s",
        "strike",
        "sub",
        "sup",
        "small",
        "mark",
        # Headers
        "h1",
        "h2",
        "h3",
        "h4",
        "h5",
        "h6",
        # Lists
        "ul",
        "ol",
        "li",
        # Links (with restrictions on attributes)
        "a",
        # Block elements
        "blockquote",
        "pre",
        "code",
        "div",
        "span",
        # Tables
        "table",
        "thead",
        "tbody",
        "tfoot",
        "tr",
        "th",
        "td",
    ]
)

# Allowed attributes for each tag
ALLOWED_ATTRIBUTES = {
    "a": ["href", "title", "rel", "target"],
    "img": ["src", "alt", "title", "width", "height"],
    "div": ["class"],
    "span": ["class"],
    "p": ["class"],
    "table": ["class"],
    "th": ["class", "colspan", "rowspan"],
    "td": ["class", "colspan", "rowspan"],
    "*": ["class"],  # Allow class on all elements
}

# Allowed URL protocols
ALLOWED_PROTOCOLS = frozenset(
    [
        "http",
        "https",
        "mailto",
        "tel",
    ]
)

# Minimal tags for comments and short text
MINIMAL_TAGS = frozenset(
    [
        "p",
        "br",
        "strong",
        "b",
        "em",
        "i",
        "a",
    ]
)

# Tags allowed in icon SVGs (for icon template rendering)
SVG_TAGS = frozenset(
    [
        "svg",
        "path",
        "g",
        "circle",
        "rect",
        "line",
        "polyline",
        "polygon",
        "ellipse",
        "text",
        "tspan",
        "defs",
        "use",
        "symbol",
        "clipPath",
        "mask",
        "linearGradient",
        "radialGradient",
        "stop",
        "title",
    ]
)

SVG_ATTRIBUTES = {
    "svg": ["viewBox", "width", "height", "fill", "stroke", "class", "xmlns", "aria-hidden", "role"],
    "path": [
        "d",
        "fill",
        "stroke",
        "stroke-width",
        "stroke-linecap",
        "stroke-linejoin",
        "class",
        "fill-rule",
        "clip-rule",
    ],
    "g": ["fill", "stroke", "transform", "class"],
    "circle": ["cx", "cy", "r", "fill", "stroke", "class"],
    "rect": ["x", "y", "width", "height", "rx", "ry", "fill", "stroke", "class"],
    "line": ["x1", "y1", "x2", "y2", "stroke", "stroke-width", "class"],
    "polyline": ["points", "fill", "stroke", "class"],
    "polygon": ["points", "fill", "stroke", "class"],
    "*": ["class", "fill", "stroke"],
}


# =============================================================================
# Sanitization Functions
# =============================================================================


def sanitize_html(
    html: str | None,
    allowed_tags: frozenset | None = None,
    allowed_attributes: dict | None = None,
    allowed_protocols: frozenset | None = None,
    strip: bool = True,
) -> str:
    """
    Sanitize HTML content to prevent XSS attacks.

    Args:
        html: The HTML string to sanitize
        allowed_tags: Set of allowed HTML tag names
        allowed_attributes: Dict mapping tag names to allowed attributes
        allowed_protocols: Set of allowed URL protocols
        strip: If True, remove disallowed tags; if False, escape them

    Returns:
        Sanitized HTML string safe for rendering

    Example:
        >>> sanitize_html('<script>alert("xss")</script><p>Hello</p>')
        '<p>Hello</p>'
    """
    if not html:
        return ""

    if not isinstance(html, str):
        html = str(html)

    if not BLEACH_AVAILABLE:
        # Fallback: escape all HTML if bleach is not available
        return html_escape(html)

    tags = allowed_tags if allowed_tags is not None else ALLOWED_TAGS
    attrs = allowed_attributes if allowed_attributes is not None else ALLOWED_ATTRIBUTES
    protocols = allowed_protocols if allowed_protocols is not None else ALLOWED_PROTOCOLS

    return bleach.clean(
        html,
        tags=tags,
        attributes=attrs,
        protocols=protocols,
        strip=strip,
    )


def sanitize_minimal(html: str | None) -> str:
    """
    Sanitize HTML with minimal allowed tags.

    Use this for user comments, short descriptions, etc.

    Args:
        html: The HTML string to sanitize

    Returns:
        Sanitized HTML with only basic formatting tags allowed
    """
    return sanitize_html(
        html,
        allowed_tags=MINIMAL_TAGS,
        allowed_attributes={"a": ["href", "title"]},
    )


def sanitize_svg(svg: str | None) -> str:
    """
    Sanitize SVG content for safe inline rendering.

    This is specifically for icon SVGs that need to be rendered inline.
    Removes potentially dangerous elements while preserving SVG structure.

    Args:
        svg: The SVG string to sanitize

    Returns:
        Sanitized SVG string safe for inline rendering
    """
    if not svg:
        return ""

    if not isinstance(svg, str):
        svg = str(svg)

    if not BLEACH_AVAILABLE:
        # Fallback: escape all if bleach is not available
        return html_escape(svg)

    return bleach.clean(
        svg,
        tags=SVG_TAGS,
        attributes=SVG_ATTRIBUTES,
        strip=True,
    )


def strip_html(html: str | None) -> str:
    """
    Remove all HTML tags from a string.

    Use this for contexts where no HTML is allowed at all.

    Args:
        html: The HTML string to strip

    Returns:
        Plain text with all HTML tags removed
    """
    if not html:
        return ""

    if not isinstance(html, str):
        html = str(html)

    if BLEACH_AVAILABLE:
        return bleach.clean(html, tags=[], strip=True)
    else:
        # Fallback: use regex to strip tags
        return re.sub(r"<[^>]+>", "", html)


# =============================================================================
# JSON/JavaScript Context Sanitization
# =============================================================================


def sanitize_for_json(data: Any) -> str:
    """
    Safely serialize data for embedding in JavaScript/JSON contexts.

    This prevents XSS when embedding data in <script> tags or JavaScript.

    Args:
        data: The data to serialize (dict, list, or primitive)

    Returns:
        JSON string safe for embedding in JavaScript

    Example:
        >>> sanitize_for_json({'name': '</script><script>alert("xss")'})
        '{"name": "\\u003c/script\\u003e\\u003cscript\\u003ealert(\\"xss\\")"}'
    """
    # JSON encode with safe characters escaped
    return (
        json.dumps(data, ensure_ascii=False)
        .replace("<", "\\u003c")
        .replace(">", "\\u003e")
        .replace("&", "\\u0026")
        .replace("'", "\\u0027")
    )


def escape_js_string(s: str | None) -> str:
    """
    Escape a string for safe use in JavaScript string literals.

    Args:
        s: The string to escape

    Returns:
        Escaped string safe for JavaScript contexts
    """
    if not s:
        return ""

    if not isinstance(s, str):
        s = str(s)

    # Escape backslashes first, then other special characters
    return (
        s.replace("\\", "\\\\")
        .replace("'", "\\'")
        .replace('"', '\\"')
        .replace("\n", "\\n")
        .replace("\r", "\\r")
        .replace("<", "\\u003c")
        .replace(">", "\\u003e")
        .replace("&", "\\u0026")
    )


# =============================================================================
# URL Sanitization
# =============================================================================


def sanitize_url(url: str | None, allowed_protocols: frozenset | None = None) -> str:
    """
    Sanitize a URL to prevent javascript: and other dangerous protocols.

    Args:
        url: The URL to sanitize
        allowed_protocols: Set of allowed URL protocols

    Returns:
        Sanitized URL or empty string if unsafe
    """
    if not url:
        return ""

    if not isinstance(url, str):
        url = str(url)

    url = url.strip()

    if not url:
        return ""

    protocols = allowed_protocols if allowed_protocols is not None else ALLOWED_PROTOCOLS

    # Check for allowed protocols
    url_lower = url.lower()

    # Check for javascript:, data:, vbscript:, etc.
    if ":" in url_lower:
        protocol = url_lower.split(":")[0]
        if protocol not in protocols:  # noqa: SIM102
            # Allow relative URLs and anchor links
            if not (url.startswith("/") or url.startswith("#") or url.startswith("?")):
                return ""

    return url


# =============================================================================
# Attribute Sanitization
# =============================================================================


def sanitize_attribute_value(value: str | None) -> str:
    """
    Sanitize a value for use in HTML attributes.

    Args:
        value: The attribute value to sanitize

    Returns:
        Sanitized value safe for HTML attribute contexts
    """
    if not value:
        return ""

    if not isinstance(value, str):
        value = str(value)

    # HTML escape for attribute context
    return html_escape(value, quote=True)


def sanitize_class_name(name: str | None) -> str:
    """
    Sanitize a CSS class name.

    Args:
        name: The class name to sanitize

    Returns:
        Sanitized class name containing only safe characters
    """
    if not name:
        return ""

    if not isinstance(name, str):
        name = str(name)

    # Only allow alphanumeric, hyphens, and underscores
    return re.sub(r"[^a-zA-Z0-9_-]", "", name)
