"""
自定义验证器

提供常用的 Pydantic 验证器和验证函数
包括输入清理以防止 SQL 注入和 XSS 攻击
"""
import re
import html
from typing import Any, Optional
from pydantic import field_validator, ValidationInfo
from src.utils.errors import InvalidParameterException


# 正则表达式模式
URL_PATTERN = re.compile(
    r'^https?://'  # 超文本传输协议:// or https://
    r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|'  # 域名...
    r'localhost|'  # 局部的host...
    r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'  # ...or ip
    r'(?::\d+)?'  # 可选 port
    r'(?:/?|[/?]\S+)$', re.IGNORECASE
)

UUID_PATTERN = re.compile(
    r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$',
    re.IGNORECASE
)

ASPECT_RATIO_PATTERN = re.compile(r'^\d+:\d+$')

SIZE_PATTERN = re.compile(r'^\d+\*\d+$')

# SQL 注入检测模式
SQL_INJECTION_PATTERNS = [
    re.compile(r"(\bUNION\b.*\bSELECT\b)", re.IGNORECASE),
    re.compile(r"(\bSELECT\b.*\bFROM\b)", re.IGNORECASE),
    re.compile(r"(\bINSERT\b.*\bINTO\b)", re.IGNORECASE),
    re.compile(r"(\bUPDATE\b.*\bSET\b)", re.IGNORECASE),
    re.compile(r"(\bDELETE\b.*\bFROM\b)", re.IGNORECASE),
    re.compile(r"(\bDROP\b.*\bTABLE\b)", re.IGNORECASE),
    re.compile(r"(\bEXEC\b|\bEXECUTE\b)", re.IGNORECASE),
    re.compile(r"(--\s|;\s*--|/\*|\*/)", re.IGNORECASE),  # SQL comments
    re.compile(r"(;\s*DROP\b)", re.IGNORECASE),  # Drop after semicolon
    re.compile(r"(;\s*DELETE\b)", re.IGNORECASE),  # 删除 after semicolon
    re.compile(r"('\s*OR\s*'[^']*'=')", re.IGNORECASE),  # '1'='1' pattern
    re.compile(r"(admin'\s*--)", re.IGNORECASE),  # admin'-- pattern
]

# 跨站脚本攻击 检测模式
XSS_PATTERNS = [
    re.compile(r"<script[^>]*>.*?</script>", re.IGNORECASE | re.DOTALL),
    re.compile(r"javascript:", re.IGNORECASE),
    re.compile(r"on\w+\s*=", re.IGNORECASE),  # 事件 handlers like onclick=
    re.compile(r"<iframe[^>]*>", re.IGNORECASE),
    re.compile(r"<object[^>]*>", re.IGNORECASE),
    re.compile(r"<embed[^>]*>", re.IGNORECASE),
]


def sanitize_string(value: str, field_name: str = "value", allow_html: bool = False) -> str:
    """清理字符串输入以防止注入攻击
    
    Args:
        value: 输入字符串
        field_name: 字段名称（用于错误消息）
        allow_html: 是否允许 HTML（如果为 False，将转义 HTML）
    
    Returns:
        str: 清理后的字符串
    
    Raises:
        InvalidParameterException: 检测到恶意输入
    """
    if not isinstance(value, str):
        return value
    
    # 检测 SQL 注入
    for pattern in SQL_INJECTION_PATTERNS:
        if pattern.search(value):
            raise InvalidParameterException(
                field_name,
                f"Potentially malicious input detected in {field_name}"
            )
    
    # 检测 XSS
    for pattern in XSS_PATTERNS:
        if pattern.search(value):
            raise InvalidParameterException(
                field_name,
                f"Potentially malicious script detected in {field_name}"
            )
    
    # 如果不允许 HTML，转义 HTML 字符
    if not allow_html:
        value = html.escape(value)
    
    return value


def sanitize_dict(data: dict, allow_html: bool = False) -> dict:
    """递归清理字典中的所有字符串值
    
    Args:
        data: 输入字典
        allow_html: 是否允许 HTML
    
    Returns:
        dict: 清理后的字典
    """
    if not isinstance(data, dict):
        return data
    
    sanitized = {}
    for key, value in data.items():
        if isinstance(value, str):
            sanitized[key] = sanitize_string(value, key, allow_html)
        elif isinstance(value, dict):
            sanitized[key] = sanitize_dict(value, allow_html)
        elif isinstance(value, list):
            sanitized[key] = [
                sanitize_string(item, key, allow_html) if isinstance(item, str)
                else sanitize_dict(item, allow_html) if isinstance(item, dict)
                else item
                for item in value
            ]
        else:
            sanitized[key] = value
    
    return sanitized


def validate_url(url: str) -> str:
    """验证 URL 格式
    
    Args:
        url: URL 字符串
    
    Returns:
        str: 验证通过的 URL
    
    Raises:
        InvalidParameterException: URL 格式无效
    """
    if not url or not isinstance(url, str):
        raise InvalidParameterException("url", "URL cannot be empty")
    
    if not URL_PATTERN.match(url):
        raise InvalidParameterException("url", f"Invalid URL format: {url}")
    
    return url


def validate_uuid(value: str, field_name: str = "id") -> str:
    """验证 UUID 格式
    
    Args:
        value: UUID 字符串
        field_name: 字段名称（用于错误消息）
    
    Returns:
        str: 验证通过的 UUID
    
    Raises:
        InvalidParameterException: UUID 格式无效
    """
    if not value or not isinstance(value, str):
        raise InvalidParameterException(field_name, f"{field_name} cannot be empty")
    
    if not UUID_PATTERN.match(value):
        raise InvalidParameterException(field_name, f"Invalid UUID format: {value}")
    
    return value


def validate_aspect_ratio(ratio: str) -> str:
    """验证宽高比格式
    
    Args:
        ratio: 宽高比字符串，格式: "16:9"
    
    Returns:
        str: 验证通过的宽高比
    
    Raises:
        InvalidParameterException: 宽高比格式无效
    """
    if not ratio or not isinstance(ratio, str):
        raise InvalidParameterException("aspect_ratio", "Aspect ratio cannot be empty")
    
    if not ASPECT_RATIO_PATTERN.match(ratio):
        raise InvalidParameterException(
            "aspect_ratio",
            f"Invalid aspect ratio format: {ratio}. Expected format: 'width:height' (e.g., '16:9')"
        )
    
    return ratio


def validate_size(size: str) -> str:
    """验证尺寸格式
    
    Args:
        size: 尺寸字符串，格式: "1920*1080"
    
    Returns:
        str: 验证通过的尺寸
    
    Raises:
        InvalidParameterException: 尺寸格式无效
    """
    if not size or not isinstance(size, str):
        raise InvalidParameterException("size", "Size cannot be empty")
    
    if not SIZE_PATTERN.match(size):
        raise InvalidParameterException(
            "size",
            f"Invalid size format: {size}. Expected format: 'width*height' (e.g., '1920*1080')"
        )
    
    return size


def validate_positive_int(value: int, field_name: str = "value") -> int:
    """验证正整数
    
    Args:
        value: 整数值
        field_name: 字段名称（用于错误消息）
    
    Returns:
        int: 验证通过的整数
    
    Raises:
        InvalidParameterException: 值不是正整数
    """
    if not isinstance(value, int) or value <= 0:
        raise InvalidParameterException(field_name, f"{field_name} must be a positive integer")
    
    return value


def validate_non_empty_string(value: str, field_name: str = "value", sanitize: bool = True) -> str:
    """验证非空字符串
    
    Args:
        value: 字符串值
        field_name: 字段名称（用于错误消息）
        sanitize: 是否清理输入
    
    Returns:
        str: 验证通过的字符串
    
    Raises:
        InvalidParameterException: 字符串为空或包含恶意内容
    """
    if not value or not isinstance(value, str) or not value.strip():
        raise InvalidParameterException(field_name, f"{field_name} cannot be empty")
    
    value = value.strip()
    
    # 清理输入
    if sanitize:
        value = sanitize_string(value, field_name, allow_html=False)
    
    return value


def validate_enum(value: str, allowed_values: list, field_name: str = "value") -> str:
    """验证枚举值
    
    Args:
        value: 值
        allowed_values: 允许的值列表
        field_name: 字段名称（用于错误消息）
    
    Returns:
        str: 验证通过的值
    
    Raises:
        InvalidParameterException: 值不在允许的列表中
    """
    if value not in allowed_values:
        raise InvalidParameterException(
            field_name,
            f"Invalid {field_name}: {value}. Allowed values: {', '.join(allowed_values)}"
        )
    
    return value


def validate_range(
    value: float,
    min_value: Optional[float] = None,
    max_value: Optional[float] = None,
    field_name: str = "value"
) -> float:
    """验证数值范围
    
    Args:
        value: 数值
        min_value: 最小值（可选）
        max_value: 最大值（可选）
        field_name: 字段名称（用于错误消息）
    
    Returns:
        float: 验证通过的数值
    
    Raises:
        InvalidParameterException: 值超出范围
    """
    if min_value is not None and value < min_value:
        raise InvalidParameterException(
            field_name,
            f"{field_name} must be >= {min_value}, got {value}"
        )
    
    if max_value is not None and value > max_value:
        raise InvalidParameterException(
            field_name,
            f"{field_name} must be <= {max_value}, got {value}"
        )
    
    return value


def validate_list_not_empty(value: list, field_name: str = "value") -> list:
    """验证列表非空
    
    Args:
        value: 列表
        field_name: 字段名称（用于错误消息）
    
    Returns:
        list: 验证通过的列表
    
    Raises:
        InvalidParameterException: 列表为空
    """
    if not value or not isinstance(value, list):
        raise InvalidParameterException(field_name, f"{field_name} cannot be empty")
    
    return value


# Pydantic 字段验证器装饰器示例
class ValidatorMixin:
    """验证器 Mixin 类
    
    提供常用的 Pydantic 字段验证器
    可以被 Pydantic 模型继承使用
    """
    
    @field_validator('prompt', mode='before')
    @classmethod
    def validate_prompt(cls, v: Any) -> str:
        """验证 prompt 字段"""
        return validate_non_empty_string(v, "prompt")
    
    @field_validator('aspect_ratio', mode='before')
    @classmethod
    def validate_aspect_ratio_field(cls, v: Any) -> Optional[str]:
        """验证 aspect_ratio 字段"""
        if v is None:
            return v
        return validate_aspect_ratio(v)
    
    @field_validator('size', mode='before')
    @classmethod
    def validate_size_field(cls, v: Any) -> Optional[str]:
        """验证 size 字段"""
        if v is None:
            return v
        return validate_size(v)
    
    @field_validator('image_inputs', 'video_inputs', 'reference_images', mode='before')
    @classmethod
    def validate_url_list(cls, v: Any, info: ValidationInfo) -> Optional[list]:
        """验证 URL 列表字段"""
        if v is None:
            return v
        
        if not isinstance(v, list):
            raise InvalidParameterException(info.field_name, f"{info.field_name} must be a list")
        
        # 验证每个 URL
        for url in v:
            validate_url(url)
        
        return v


# 常用验证规则常量
VALID_IMAGE_MODELS = ["flux-dev", "flux-pro", "sd-3", "dall-e-3"]
VALID_VIDEO_MODELS = ["kling-v1", "kling-v1-5", "runway-gen3"]
VALID_TASK_TYPES = ["image", "video", "audio", "script"]
VALID_TASK_STATUSES = ["pending", "queued", "processing", "succeeded", "failed", "cancelled", "timeout"]
VALID_PROJECT_TYPES = ["video"]
VALID_ASSET_TYPES = ["character", "scene", "prop", "other"]
