"""
Validators for puck_data and embed URLs.

Provides security validation for site builder content.
"""
import json
from rest_framework.exceptions import ValidationError


# Maximum size for puck_data (5MB)
MAX_PUCK_DATA_SIZE = 5 * 1024 * 1024

# Disallowed patterns for XSS prevention
DISALLOWED_PATTERNS = [
    '<script',
    '</script',
    'javascript:',
    'onerror=',
    'onload=',
    'onclick=',
    'onmouseover=',
    'onfocus=',
    'onblur=',
    'onsubmit=',
    'onchange=',
    'data:text/html',
]

# Allowed embed domains
ALLOWED_EMBED_DOMAINS = [
    'www.google.com/maps/embed',
    'maps.google.com',
    'www.openstreetmap.org',
]


def validate_embed_url(url: str) -> bool:
    """
    Validate that an embed URL is from an allowed domain.

    Args:
        url: The URL to validate

    Returns:
        True if URL is allowed, False otherwise
    """
    if not url:
        return False

    # Must be HTTPS
    if not url.startswith('https://'):
        return False

    # Check against allowed domains
    for domain in ALLOWED_EMBED_DOMAINS:
        if url.startswith(f'https://{domain}'):
            return True

    return False


def validate_puck_data(value: dict) -> dict:
    """
    Validate puck_data structure and content for security.

    Args:
        value: The puck_data dict to validate

    Returns:
        The validated puck_data dict

    Raises:
        ValidationError: If validation fails
    """
    if not isinstance(value, dict):
        raise ValidationError("puck_data must be a dictionary")

    # Check size limit
    serialized = json.dumps(value)
    if len(serialized) > MAX_PUCK_DATA_SIZE:
        raise ValidationError(
            f"Page data too large. Maximum size is {MAX_PUCK_DATA_SIZE // (1024*1024)}MB"
        )

    # Validate structure
    if 'content' not in value:
        raise ValidationError("Invalid puck_data structure: missing 'content' key")

    if not isinstance(value.get('content'), list):
        raise ValidationError("Invalid puck_data structure: 'content' must be a list")

    # Check for disallowed content (XSS prevention)
    serialized_lower = serialized.lower()
    for pattern in DISALLOWED_PATTERNS:
        if pattern.lower() in serialized_lower:
            raise ValidationError(
                f"Disallowed content detected: content contains potentially unsafe pattern"
            )

    # Validate component structure
    for i, component in enumerate(value.get('content', [])):
        _validate_component(component, f'content[{i}]')

    # Validate zones if present
    zones = value.get('zones', {})
    if zones and isinstance(zones, dict):
        for zone_name, zone_content in zones.items():
            if isinstance(zone_content, list):
                for i, component in enumerate(zone_content):
                    _validate_component(component, f'zones.{zone_name}[{i}]')

    return value


def _validate_component(component: dict, path: str) -> None:
    """
    Validate a single component structure.

    Args:
        component: The component dict to validate
        path: The path in the data structure (for error messages)

    Raises:
        ValidationError: If component is invalid
    """
    if not isinstance(component, dict):
        raise ValidationError(f"Invalid component at {path}: must be a dictionary")

    if 'type' not in component:
        raise ValidationError(f"Invalid component at {path}: missing 'type' key")

    component_type = component.get('type')
    if not isinstance(component_type, str):
        raise ValidationError(f"Invalid component at {path}: 'type' must be a string")

    # Validate props if present
    props = component.get('props', {})
    if props and isinstance(props, dict):
        _validate_props(props, f'{path}.props')


def _validate_props(props: dict, path: str) -> None:
    """
    Validate component props for potentially unsafe content.

    Args:
        props: The props dict to validate
        path: The path in the data structure (for error messages)

    Raises:
        ValidationError: If props contain unsafe content
    """
    # Event handler prop names that are not allowed
    EVENT_HANDLER_PROPS = [
        'onclick', 'onload', 'onerror', 'onmouseover', 'onfocus',
        'onblur', 'onsubmit', 'onchange', 'onkeydown', 'onkeyup'
    ]

    for key, value in props.items():
        # Check for event handler prop names
        if key.lower() in EVENT_HANDLER_PROPS:
            raise ValidationError(
                f"Disallowed prop at {path}.{key}: event handler props are not allowed"
            )
        # Check string values for URL safety
        if isinstance(value, str):
            # Check for javascript: URLs in link-related props
            if key in ('href', 'link', 'url', 'src', 'ctaLink', 'embedUrl'):
                value_lower = value.lower().strip()
                if value_lower.startswith('javascript:'):
                    raise ValidationError(
                        f"Disallowed content at {path}.{key}: javascript: URLs are not allowed"
                    )
                if value_lower.startswith('data:') and 'text/html' in value_lower:
                    raise ValidationError(
                        f"Disallowed content at {path}.{key}: data: URLs with HTML are not allowed"
                    )

            # Check for event handler patterns in any string
            value_lower = value.lower()
            for pattern in ['onerror=', 'onload=', 'onclick=', 'onmouseover=']:
                if pattern in value_lower:
                    raise ValidationError(
                        f"Disallowed content at {path}.{key}: event handlers are not allowed"
                    )

        # Recursively validate nested objects
        elif isinstance(value, dict):
            _validate_props(value, f'{path}.{key}')

        # Validate arrays
        elif isinstance(value, list):
            for i, item in enumerate(value):
                if isinstance(item, dict):
                    _validate_props(item, f'{path}.{key}[{i}]')
                elif isinstance(item, str):
                    # Check string items in arrays
                    item_lower = item.lower()
                    for pattern in DISALLOWED_PATTERNS:
                        if pattern.lower() in item_lower:
                            raise ValidationError(
                                f"Disallowed content at {path}.{key}[{i}]"
                            )
