"""HTML sanitization utilities for FenLiu."""

import html
import re
from urllib.parse import unquote


def strip_html_tags(text: str | None) -> str:
    """Remove HTML tags and decode entities from text.

    This function:
    1. Decodes URL-encoded entities (e.g., %3C -> <)
    2. Decodes HTML entities (e.g., &lt; -> <)
    3. Removes HTML tags (including multiline)
    4. Removes HTML comments
    5. Removes any remaining < or > characters

    Args:
        text: Input text, may be None

    Returns:
        Clean text without HTML tags

    """
    if not text:
        return ""

    result = text

    # First try to decode URL-encoded text
    try:
        result = unquote(result, errors="strict")
    except Exception:
        # If URL decoding fails, try to decode common URL-encoded HTML entities
        replacements = [
            ("%3C", "<"),
            ("%3E", ">"),
            ("%22", '"'),
            ("%27", "'"),
            ("%20", " "),
            ("%2F", "/"),
            ("%3A", ":"),
            ("%3F", "?"),
            ("%3D", "="),
            ("%26", "&"),
        ]
        for encoded, decoded in replacements:
            result = result.replace(encoded, decoded)
            result = result.replace(encoded.lower(), decoded)

    # Decode HTML entities
    result = html.unescape(result)

    # Strip HTML tags (including multiline tags)
    result = re.sub(r"<[^>]*>", "", result)

    # Also remove HTML comments
    result = re.sub(r"<!--[\s\S]*?-->", "", result)

    # Remove any remaining < or > characters (in case of incomplete tags)
    result = result.replace("<", "").replace(">", "")

    return result


def escape_html(text: str | None) -> str:
    """Escape HTML special characters for safe use in HTML content.

    This escapes: &, <, >, ", and '

    Args:
        text: Input text, may be None

    Returns:
        HTML-escaped text

    """
    if not text:
        return ""

    # html.escape with quote=True escapes ", ' as &quot; and &#x27;
    return html.escape(text, quote=True)


def clean_url(url: str | None) -> str:
    """Sanitize a URL for safe use in href/src attributes.

    This function:
    1. Removes HTML tags and comments
    2. Removes control characters and unusual whitespace
    3. Validates URL protocols (only allows http://, https://, /, @)
    4. Blocks dangerous protocols (javascript:, data:, vbscript:, file:)
    5. Escapes HTML entities for safe use in attribute values

    Args:
        url: Input URL, may be None

    Returns:
        Sanitized URL, or empty string if invalid/dangerous

    """
    if not url:
        return ""

    result = url.strip()

    # Remove any HTML tags - these should never be in a URL
    result = re.sub(r"<[^>]*>", "", result)

    # Remove HTML comments
    result = re.sub(r"<!--[\s\S]*?-->", "", result)

    # Remove control characters and unusual whitespace
    result = re.sub(r"[\x00-\x1F\x7F]", "", result)
    result = re.sub(r"\s+", " ", result)

    # Escape HTML entities for safe use in attribute values
    result = escape_html(result)

    # Basic URL validation
    # Allow http://, https://, or relative URLs starting with /
    # Also allow fediverse mentions like @user@instance
    if not re.match(r"^(https?://|/|@)", result):
        return ""

    # Additional safety: check for dangerous protocols
    dangerous_patterns = [
        r"^javascript:",
        r"^data:",
        r"^vbscript:",
        r"^file:",
    ]
    for pattern in dangerous_patterns:
        if re.match(pattern, result, re.IGNORECASE):
            return ""

    return result


def sanitize_post_content(content: str | None, max_length: int | None = None) -> str:
    """Sanitize post content for safe display.

    This function applies strip_html_tags and escape_html, and optionally
    truncates to a maximum length.

    Args:
        content: Post content, may be None
        max_length: Maximum length for truncated content

    Returns:
        Sanitized post content

    """
    if not content:
        return ""

    # Strip HTML tags
    cleaned = strip_html_tags(content)

    # Escape HTML
    escaped = escape_html(cleaned)

    # Truncate if needed
    if max_length is not None and len(escaped) > max_length:
        # Try to truncate at word boundary
        if len(escaped) > max_length:
            truncated = escaped[:max_length]
            # Find last space to avoid cutting words
            last_space = truncated.rfind(" ")
            if last_space > max_length * 0.8:  # Only if we have a reasonable space
                truncated = truncated[:last_space]
            return truncated + "..."

    return escaped


__all__ = [
    "clean_url",
    "escape_html",
    "sanitize_post_content",
    "strip_html_tags",
]
