#!/usr/bin/env python3
"""
Plugin Parameter Validation System

Provides comprehensive validation for plugin parameters based on their schema.
Supports type validation, required checks, default values, and constraints.
"""

from typing import Any, Optional, Dict, List
from dataclasses import dataclass


@dataclass
class ValidationError:
    """Represents a validation error for a specific field."""
    field: str
    message: str
    type: str  # 'required', 'type', 'constraint', 'custom'


class ValidationResult:
    """Result of parameter validation."""
    
    def __init__(self):
        self.errors: List[ValidationError] = []
    
    def add_error(self, field: str, message: str, error_type: str = 'validation'):
        """Add a validation error."""
        self.errors.append(ValidationError(field, message, error_type))
    
    def is_valid(self) -> bool:
        """Check if validation passed."""
        return len(self.errors) == 0
    
    def get_error_message(self) -> str:
        """Get a single error message (first error)."""
        if not self.errors:
            return ""
        return self.errors[0].message
    
    def get_detailed_errors(self) -> Dict[str, List[Dict[str, str]]]:
        """Get detailed errors grouped by field."""
        errors_by_field: Dict[str, List[Dict[str, str]]] = {}
        
        for error in self.errors:
            if error.field not in errors_by_field:
                errors_by_field[error.field] = []
            
            errors_by_field[error.field].append({
                'message': error.message,
                'type': error.type
            })
        
        return errors_by_field


class PluginValidator:
    """
    Validates plugin parameters against their schema.
    
    Performs:
    - Required parameter checking
    - Type validation
    - Constraint validation (min/max for numbers, pattern for strings)
    - Custom validation
    """
    
    def apply_defaults(self, plugin: Any, params: dict) -> dict:
        """
        Apply default values from plugin parameter schema.
        
        Args:
            plugin: Plugin instance with metadata
            params: Request parameters
            
        Returns:
            Parameters with defaults applied
        """
        result = params.copy()
        
        for param_def in plugin.metadata.parameters:
            # Apply default if parameter not provided and has a default
            if param_def.name not in result and param_def.default is not None:
                result[param_def.name] = param_def.default
        
        return result
    
    def validate(self, plugin: Any, params: dict) -> ValidationResult:
        """
        Validate parameters against plugin schema.
        
        Args:
            plugin: Plugin instance with metadata
            params: Request parameters to validate
            
        Returns:
            ValidationResult with any errors found
        """
        result = ValidationResult()
        
        # Custom validation first (if plugin provides it)
        self._run_custom_validation(plugin, params, result)
        
        # Schema-based validation
        for param_def in plugin.metadata.parameters:
            self._validate_parameter(param_def, params, result)
        
        return result
    
    def _run_custom_validation(self, plugin: Any, params: dict, result: ValidationResult):
        """Run plugin's custom validation if provided."""
        if hasattr(plugin, 'validate_params'):
            try:
                custom_error = plugin.validate_params(params)
                if custom_error:
                    result.add_error('_custom', custom_error, 'custom')
            except Exception as e:
                result.add_error('_custom', f"Validation error: {str(e)}", 'custom')
    
    def _validate_parameter(self, param_def, params: dict, result: ValidationResult):
        """Validate a single parameter."""
        param_name = param_def.name
        
        # Check required parameters
        if param_def.required and param_name not in params:
            result.add_error(
                param_name,
                f"Missing required parameter: '{param_name}'",
                'required'
            )
            return
        
        # Skip validation if parameter not provided and not required
        if param_name not in params:
            return
        
        value = params[param_name]
        
        # Type-specific validation
        self._validate_type(param_def, value, result)
        
        # Constraint validation
        self._validate_constraints(param_def, value, result)
    
    def _validate_type(self, param_def, value: Any, result: ValidationResult):
        """Validate parameter type."""
        param_type = param_def.type
        param_name = param_def.name
        
        # Number validation
        if param_type == 'number':
            if not isinstance(value, (int, float)):
                result.add_error(
                    param_name,
                    f"Parameter '{param_name}' must be a number",
                    'type'
                )
        
        # Checkbox (boolean) validation
        elif param_type == 'checkbox':
            if not isinstance(value, bool):
                result.add_error(
                    param_name,
                    f"Parameter '{param_name}' must be a boolean",
                    'type'
                )
        
        # Text/textarea/url validation (must be string)
        elif param_type in ('text', 'textarea', 'url'):
            if not isinstance(value, str):
                result.add_error(
                    param_name,
                    f"Parameter '{param_name}' must be a string",
                    'type'
                )
            elif param_type == 'url':
                # URL format validation (basic)
                if not (value.startswith('http://') or value.startswith('https://')):
                    result.add_error(
                        param_name,
                        f"Parameter '{param_name}' must be a valid URL (http:// or https://)",
                        'type'
                    )
        
        # Select validation (value must be in options)
        elif param_type == 'select':
            if param_def.options and value not in param_def.options:
                options_str = ', '.join(param_def.options)
                result.add_error(
                    param_name,
                    f"Parameter '{param_name}' must be one of: {options_str}",
                    'type'
                )
    
    def _validate_constraints(self, param_def, value: Any, result: ValidationResult):
        """
        Validate parameter constraints (min/max for numbers, length for strings, etc.).
        
        Looks for additional attributes on param_def:
        - min: Minimum value for numbers or minimum length for strings
        - max: Maximum value for numbers or maximum length for strings
        - pattern: Regex pattern for string validation
        """
        param_name = param_def.name
        param_type = param_def.type
        
        # Number constraints (min/max)
        if param_type == 'number' and isinstance(value, (int, float)):
            if hasattr(param_def, 'min') and param_def.min is not None:
                if value < param_def.min:
                    result.add_error(
                        param_name,
                        f"Parameter '{param_name}' must be at least {param_def.min}",
                        'constraint'
                    )
            
            if hasattr(param_def, 'max') and param_def.max is not None:
                if value > param_def.max:
                    result.add_error(
                        param_name,
                        f"Parameter '{param_name}' must be at most {param_def.max}",
                        'constraint'
                    )
        
        # String constraints (length)
        if param_type in ('text', 'textarea', 'url') and isinstance(value, str):
            if hasattr(param_def, 'min_length') and param_def.min_length is not None:
                if len(value) < param_def.min_length:
                    result.add_error(
                        param_name,
                        f"Parameter '{param_name}' must be at least {param_def.min_length} characters",
                        'constraint'
                    )
            
            if hasattr(param_def, 'max_length') and param_def.max_length is not None:
                if len(value) > param_def.max_length:
                    result.add_error(
                        param_name,
                        f"Parameter '{param_name}' must be at most {param_def.max_length} characters",
                        'constraint'
                    )
            
            # Pattern validation (regex)
            if hasattr(param_def, 'pattern') and param_def.pattern is not None:
                import re
                if not re.match(param_def.pattern, value):
                    pattern_desc = getattr(param_def, 'pattern_description', 'the required format')
                    result.add_error(
                        param_name,
                        f"Parameter '{param_name}' must match {pattern_desc}",
                        'constraint'
                    )


# Global validator instance
_validator: Optional[PluginValidator] = None


def get_validator() -> PluginValidator:
    """Get the global validator instance."""
    global _validator
    if _validator is None:
        _validator = PluginValidator()
    return _validator


def validate_plugin_params(plugin: Any, params: dict, detailed: bool = False) -> tuple[bool, str | dict]:
    """
    Validate plugin parameters.
    
    Args:
        plugin: Plugin instance with metadata
        params: Request parameters
        detailed: If True, return detailed error information
        
    Returns:
        Tuple of (is_valid, error_message_or_details)
    """
    validator = get_validator()
    result = validator.validate(plugin, params)
    
    if result.is_valid():
        return True, ""
    
    if detailed:
        return False, result.get_detailed_errors()
    else:
        return False, result.get_error_message()


def apply_default_values(plugin: Any, params: dict) -> dict:
    """
    Apply default values to parameters.
    
    Args:
        plugin: Plugin instance with metadata
        params: Request parameters
        
    Returns:
        Parameters with defaults applied
    """
    validator = get_validator()
    return validator.apply_defaults(plugin, params)
