"""
Erweiterte Formular-Validierung für das MYP-System
==================================================

Dieses Modul stellt umfassende Client- und serverseitige Validierung
mit benutzerfreundlichem UI-Feedback bereit.

Funktionen:
- Multi-Level-Validierung (Client/Server)
- Echtzeitvalidierung mit JavaScript
- Barrierefreie Fehlermeldungen
- Custom Validators für spezielle Anforderungen
- Automatische Sanitization von Eingaben
"""

import re
import html
import json
import logging
from typing import Dict, List, Any, Optional, Callable, Union
from datetime import datetime, timedelta
from flask import request, jsonify, session
from functools import wraps
from werkzeug.datastructures import FileStorage

from utils.logging_config import get_logger
from config.settings import ALLOWED_EXTENSIONS, MAX_FILE_SIZE

logger = get_logger("validation")

class ValidationError(Exception):
    """Custom Exception für Validierungsfehler"""
    def __init__(self, message: str, field: str = None, code: str = None):
        self.message = message
        self.field = field
        self.code = code
        super().__init__(self.message)

class ValidationResult:
    """Ergebnis einer Validierung"""
    def __init__(self):
        self.is_valid = True
        self.errors: Dict[str, List[str]] = {}
        self.warnings: Dict[str, List[str]] = {}
        self.cleaned_data: Dict[str, Any] = {}
    
    def add_error(self, field: str, message: str):
        """Fügt einen Validierungsfehler hinzu"""
        if field not in self.errors:
            self.errors[field] = []
        self.errors[field].append(message)
        self.is_valid = False
    
    def add_warning(self, field: str, message: str):
        """Fügt eine Warnung hinzu"""
        if field not in self.warnings:
            self.warnings[field] = []
        self.warnings[field].append(message)
    
    def to_dict(self) -> Dict[str, Any]:
        """Konvertiert das Ergebnis zu einem Dictionary"""
        return {
            "is_valid": self.is_valid,
            "errors": self.errors,
            "warnings": self.warnings,
            "cleaned_data": self.cleaned_data
        }

class BaseValidator:
    """Basis-Klasse für alle Validatoren"""
    
    def __init__(self, required: bool = False, allow_empty: bool = True):
        self.required = required
        self.allow_empty = allow_empty
    
    def validate(self, value: Any, field_name: str = None) -> ValidationResult:
        """Führt die Validierung durch"""
        result = ValidationResult()
        
        # Prüfung auf erforderliche Felder
        if self.required and (value is None or value == ""):
            result.add_error(field_name or "field", "Dieses Feld ist erforderlich.")
            return result
        
        # Wenn Wert leer und erlaubt, keine weitere Validierung
        if not value and self.allow_empty:
            result.cleaned_data[field_name or "field"] = value
            return result
        
        return self._validate_value(value, field_name, result)
    
    def _validate_value(self, value: Any, field_name: str, result: ValidationResult) -> ValidationResult:
        """Überschreibbar für spezifische Validierungslogik"""
        result.cleaned_data[field_name or "field"] = value
        return result

class StringValidator(BaseValidator):
    """Validator für String-Werte"""
    
    def __init__(self, min_length: int = None, max_length: int = None, 
                 pattern: str = None, trim: bool = True, **kwargs):
        super().__init__(**kwargs)
        self.min_length = min_length
        self.max_length = max_length
        self.pattern = re.compile(pattern) if pattern else None
        self.trim = trim
    
    def _validate_value(self, value: Any, field_name: str, result: ValidationResult) -> ValidationResult:
        # String konvertieren und trimmen
        str_value = str(value)
        if self.trim:
            str_value = str_value.strip()
        
        # Längenprüfung
        if self.min_length is not None and len(str_value) < self.min_length:
            result.add_error(field_name, f"Mindestlänge: {self.min_length} Zeichen")
        
        if self.max_length is not None and len(str_value) > self.max_length:
            result.add_error(field_name, f"Maximallänge: {self.max_length} Zeichen")
        
        # Pattern-Prüfung
        if self.pattern and not self.pattern.match(str_value):
            result.add_error(field_name, "Format ist ungültig")
        
        # HTML-Sanitization
        cleaned_value = html.escape(str_value)
        result.cleaned_data[field_name] = cleaned_value
        
        return result

class EmailValidator(StringValidator):
    """Validator für E-Mail-Adressen"""
    
    EMAIL_PATTERN = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    
    def __init__(self, **kwargs):
        super().__init__(pattern=self.EMAIL_PATTERN, **kwargs)
    
    def _validate_value(self, value: Any, field_name: str, result: ValidationResult) -> ValidationResult:
        result = super()._validate_value(value, field_name, result)
        
        if result.is_valid:
            # Normalisierung der E-Mail
            email = str(value).lower().strip()
            result.cleaned_data[field_name] = email
        
        return result

class IntegerValidator(BaseValidator):
    """Validator für Integer-Werte"""
    
    def __init__(self, min_value: int = None, max_value: int = None, **kwargs):
        super().__init__(**kwargs)
        self.min_value = min_value
        self.max_value = max_value
    
    def _validate_value(self, value: Any, field_name: str, result: ValidationResult) -> ValidationResult:
        try:
            int_value = int(value)
        except (ValueError, TypeError):
            result.add_error(field_name, "Muss eine ganze Zahl sein")
            return result
        
        if self.min_value is not None and int_value < self.min_value:
            result.add_error(field_name, f"Mindestwert: {self.min_value}")
        
        if self.max_value is not None and int_value > self.max_value:
            result.add_error(field_name, f"Maximalwert: {self.max_value}")
        
        result.cleaned_data[field_name] = int_value
        return result

class FloatValidator(BaseValidator):
    """Validator für Float-Werte"""
    
    def __init__(self, min_value: float = None, max_value: float = None, 
                 decimal_places: int = None, **kwargs):
        super().__init__(**kwargs)
        self.min_value = min_value
        self.max_value = max_value
        self.decimal_places = decimal_places
    
    def _validate_value(self, value: Any, field_name: str, result: ValidationResult) -> ValidationResult:
        try:
            float_value = float(value)
        except (ValueError, TypeError):
            result.add_error(field_name, "Muss eine Dezimalzahl sein")
            return result
        
        if self.min_value is not None and float_value < self.min_value:
            result.add_error(field_name, f"Mindestwert: {self.min_value}")
        
        if self.max_value is not None and float_value > self.max_value:
            result.add_error(field_name, f"Maximalwert: {self.max_value}")
        
        # Rundung auf bestimmte Dezimalstellen
        if self.decimal_places is not None:
            float_value = round(float_value, self.decimal_places)
        
        result.cleaned_data[field_name] = float_value
        return result

class DateTimeValidator(BaseValidator):
    """Validator für DateTime-Werte"""
    
    def __init__(self, format_string: str = "%Y-%m-%d %H:%M", 
                 min_date: datetime = None, max_date: datetime = None, **kwargs):
        super().__init__(**kwargs)
        self.format_string = format_string
        self.min_date = min_date
        self.max_date = max_date
    
    def _validate_value(self, value: Any, field_name: str, result: ValidationResult) -> ValidationResult:
        if isinstance(value, datetime):
            dt_value = value
        else:
            try:
                dt_value = datetime.strptime(str(value), self.format_string)
            except ValueError:
                result.add_error(field_name, f"Ungültiges Datumsformat. Erwartet: {self.format_string}")
                return result
        
        if self.min_date and dt_value < self.min_date:
            result.add_error(field_name, f"Datum muss nach {self.min_date.strftime('%d.%m.%Y')} liegen")
        
        if self.max_date and dt_value > self.max_date:
            result.add_error(field_name, f"Datum muss vor {self.max_date.strftime('%d.%m.%Y')} liegen")
        
        result.cleaned_data[field_name] = dt_value
        return result

class FileValidator(BaseValidator):
    """Validator für Datei-Uploads"""
    
    def __init__(self, allowed_extensions: List[str] = None, 
                 max_size_mb: int = None, min_size_kb: int = None, **kwargs):
        super().__init__(**kwargs)
        self.allowed_extensions = allowed_extensions or ALLOWED_EXTENSIONS
        self.max_size_mb = max_size_mb or (MAX_FILE_SIZE / (1024 * 1024))
        self.min_size_kb = min_size_kb
    
    def _validate_value(self, value: Any, field_name: str, result: ValidationResult) -> ValidationResult:
        if not isinstance(value, FileStorage):
            result.add_error(field_name, "Muss eine gültige Datei sein")
            return result
        
        # Dateiname prüfen
        if not value.filename:
            result.add_error(field_name, "Dateiname ist erforderlich")
            return result
        
        # Dateierweiterung prüfen
        extension = value.filename.rsplit('.', 1)[-1].lower() if '.' in value.filename else ''
        if extension not in self.allowed_extensions:
            result.add_error(field_name, 
                f"Nur folgende Dateiformate sind erlaubt: {', '.join(self.allowed_extensions)}")
        
        # Dateigröße prüfen
        value.seek(0, 2)  # Zum Ende der Datei
        file_size = value.tell()
        value.seek(0)  # Zurück zum Anfang
        
        if self.max_size_mb and file_size > (self.max_size_mb * 1024 * 1024):
            result.add_error(field_name, f"Datei zu groß. Maximum: {self.max_size_mb} MB")
        
        if self.min_size_kb and file_size < (self.min_size_kb * 1024):
            result.add_error(field_name, f"Datei zu klein. Minimum: {self.min_size_kb} KB")
        
        result.cleaned_data[field_name] = value
        return result

class FormValidator:
    """Haupt-Formular-Validator"""
    
    def __init__(self):
        self.fields: Dict[str, BaseValidator] = {}
        self.custom_validators: List[Callable] = []
        self.rate_limit_key = None
        self.csrf_check = True
    
    def add_field(self, name: str, validator: BaseValidator):
        """Fügt ein Feld mit Validator hinzu"""
        self.fields[name] = validator
        return self
    
    def add_custom_validator(self, validator_func: Callable):
        """Fügt einen benutzerdefinierten Validator hinzu"""
        self.custom_validators.append(validator_func)
        return self
    
    def set_rate_limit(self, key: str):
        """Setzt einen Rate-Limiting-Schlüssel"""
        self.rate_limit_key = key
        return self
    
    def disable_csrf(self):
        """Deaktiviert CSRF-Prüfung für dieses Formular"""
        self.csrf_check = False
        return self
    
    def validate(self, data: Dict[str, Any]) -> ValidationResult:
        """Validiert die gesamten Formulardaten"""
        result = ValidationResult()
        
        # Einzelfeldvalidierung
        for field_name, validator in self.fields.items():
            field_value = data.get(field_name)
            field_result = validator.validate(field_value, field_name)
            
            if not field_result.is_valid:
                result.errors.update(field_result.errors)
                result.is_valid = False
            
            result.warnings.update(field_result.warnings)
            result.cleaned_data.update(field_result.cleaned_data)
        
        # Benutzerdefinierte Validierung
        if result.is_valid:
            for custom_validator in self.custom_validators:
                try:
                    custom_result = custom_validator(result.cleaned_data)
                    if isinstance(custom_result, ValidationResult):
                        if not custom_result.is_valid:
                            result.errors.update(custom_result.errors)
                            result.is_valid = False
                        result.warnings.update(custom_result.warnings)
                except Exception as e:
                    logger.error(f"Fehler bei benutzerdefinierter Validierung: {str(e)}")
                    result.add_error("form", "Unerwarteter Validierungsfehler")
        
        return result

# Vordefinierte Formular-Validatoren
def get_user_registration_validator() -> FormValidator:
    """Validator für Benutzerregistrierung"""
    return FormValidator() \
        .add_field("username", StringValidator(min_length=3, max_length=50, required=True)) \
        .add_field("email", EmailValidator(required=True)) \
        .add_field("password", StringValidator(min_length=8, required=True)) \
        .add_field("password_confirm", StringValidator(min_length=8, required=True)) \
        .add_field("name", StringValidator(min_length=2, max_length=100, required=True)) \
        .add_custom_validator(lambda data: _validate_password_match(data))

def get_job_creation_validator() -> FormValidator:
    """Validator für Job-Erstellung"""
    return FormValidator() \
        .add_field("name", StringValidator(min_length=1, max_length=200, required=True)) \
        .add_field("description", StringValidator(max_length=500)) \
        .add_field("printer_id", IntegerValidator(min_value=1, required=True)) \
        .add_field("duration_minutes", IntegerValidator(min_value=1, max_value=1440, required=True)) \
        .add_field("start_at", DateTimeValidator(min_date=datetime.now())) \
        .add_field("file", FileValidator(required=True))

def get_printer_creation_validator() -> FormValidator:
    """Validator für Drucker-Erstellung"""
    return FormValidator() \
        .add_field("name", StringValidator(min_length=1, max_length=100, required=True)) \
        .add_field("model", StringValidator(max_length=100)) \
        .add_field("location", StringValidator(max_length=100)) \
        .add_field("ip_address", StringValidator(pattern=r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')) \
        .add_field("mac_address", StringValidator(pattern=r'^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$', required=True)) \
        .add_field("plug_ip", StringValidator(pattern=r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$', required=True)) \
        .add_field("plug_username", StringValidator(min_length=1, required=True)) \
        .add_field("plug_password", StringValidator(min_length=1, required=True))

def get_guest_request_validator() -> FormValidator:
    """Validator für Gastanfragen"""
    return FormValidator() \
        .add_field("name", StringValidator(min_length=2, max_length=100, required=True)) \
        .add_field("email", EmailValidator()) \
        .add_field("reason", StringValidator(min_length=10, max_length=500, required=True)) \
        .add_field("duration_minutes", IntegerValidator(min_value=5, max_value=480, required=True)) \
        .add_field("copies", IntegerValidator(min_value=1, max_value=10)) \
        .add_field("file", FileValidator(required=True)) \
        .set_rate_limit("guest_request")

def _validate_password_match(data: Dict[str, Any]) -> ValidationResult:
    """Validiert, ob Passwörter übereinstimmen"""
    result = ValidationResult()
    
    password = data.get("password")
    password_confirm = data.get("password_confirm")
    
    if password != password_confirm:
        result.add_error("password_confirm", "Passwörter stimmen nicht überein")
    
    return result

# Decorator für automatische Formularvalidierung
def validate_form(validator_func: Callable[[], FormValidator]):
    """Decorator für automatische Formularvalidierung"""
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            try:
                # Validator erstellen
                validator = validator_func()
                
                # Daten aus Request extrahieren
                if request.is_json:
                    data = request.get_json() or {}
                else:
                    data = dict(request.form)
                    # Dateien hinzufügen
                    for key, file in request.files.items():
                        data[key] = file
                
                # Validierung durchführen
                validation_result = validator.validate(data)
                
                # Bei Fehlern JSON-Response zurückgeben
                if not validation_result.is_valid:
                    logger.warning(f"Validierungsfehler für {request.endpoint}: {validation_result.errors}")
                    return jsonify({
                        "success": False,
                        "errors": validation_result.errors,
                        "warnings": validation_result.warnings
                    }), 400
                
                # Gereinigte Daten an die Request anhängen
                request.validated_data = validation_result.cleaned_data
                request.validation_warnings = validation_result.warnings
                
                return f(*args, **kwargs)
                
            except Exception as e:
                logger.error(f"Fehler bei Formularvalidierung: {str(e)}")
                return jsonify({
                    "success": False,
                    "errors": {"form": ["Unerwarteter Validierungsfehler"]}
                }), 500
        
        return decorated_function
    return decorator

# JavaScript für Client-seitige Validierung
def get_client_validation_js() -> str:
    """Generiert JavaScript für Client-seitige Validierung"""
    return """
    class FormValidator {
        constructor(formId, validationRules = {}) {
            this.form = document.getElementById(formId);
            this.rules = validationRules;
            this.errors = {};
            this.setupEventListeners();
        }
        
        setupEventListeners() {
            if (!this.form) return;
            
            // Echtzeit-Validierung bei Eingabe
            this.form.addEventListener('input', (e) => {
                this.validateField(e.target);
            });
            
            // Formular-Submission
            this.form.addEventListener('submit', (e) => {
                if (!this.validateForm()) {
                    e.preventDefault();
                }
            });
        }
        
        validateField(field) {
            const fieldName = field.name;
            const value = field.value;
            const rule = this.rules[fieldName];
            
            if (!rule) return true;
            
            this.clearFieldError(field);
            
            // Required-Prüfung
            if (rule.required && (!value || value.trim() === '')) {
                this.addFieldError(field, 'Dieses Feld ist erforderlich.');
                return false;
            }
            
            // Längenprüfung
            if (rule.minLength && value.length < rule.minLength) {
                this.addFieldError(field, `Mindestlänge: ${rule.minLength} Zeichen`);
                return false;
            }
            
            if (rule.maxLength && value.length > rule.maxLength) {
                this.addFieldError(field, `Maximallänge: ${rule.maxLength} Zeichen`);
                return false;
            }
            
            // Pattern-Prüfung
            if (rule.pattern && !new RegExp(rule.pattern).test(value)) {
                this.addFieldError(field, rule.patternMessage || 'Format ist ungültig');
                return false;
            }
            
            // Email-Prüfung
            if (rule.type === 'email' && value) {
                const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
                if (!emailPattern.test(value)) {
                    this.addFieldError(field, 'Bitte geben Sie eine gültige E-Mail-Adresse ein');
                    return false;
                }
            }
            
            // Custom Validierung
            if (rule.customValidator) {
                const customResult = rule.customValidator(value, field);
                if (customResult !== true) {
                    this.addFieldError(field, customResult);
                    return false;
                }
            }
            
            return true;
        }
        
        validateForm() {
            let isValid = true;
            this.errors = {};
            
            // Alle Felder validieren
            const fields = this.form.querySelectorAll('input, textarea, select');
            fields.forEach(field => {
                if (!this.validateField(field)) {
                    isValid = false;
                }
            });
            
            // Custom Form-Validierung
            if (this.rules._formValidator) {
                const formData = new FormData(this.form);
                const customResult = this.rules._formValidator(formData, this.form);
                if (customResult !== true) {
                    this.addFormError(customResult);
                    isValid = false;
                }
            }
            
            return isValid;
        }
        
        addFieldError(field, message) {
            const fieldName = field.name;
            
            // Error-Container finden oder erstellen
            let errorContainer = field.parentNode.querySelector('.field-error');
            if (!errorContainer) {
                errorContainer = document.createElement('div');
                errorContainer.className = 'field-error text-red-600 text-sm mt-1';
                errorContainer.setAttribute('role', 'alert');
                errorContainer.setAttribute('aria-live', 'polite');
                field.parentNode.appendChild(errorContainer);
            }
            
            errorContainer.textContent = message;
            field.classList.add('border-red-500');
            field.setAttribute('aria-invalid', 'true');
            
            // Für Screen Reader
            if (!field.getAttribute('aria-describedby')) {
                const errorId = `error-${fieldName}-${Date.now()}`;
                errorContainer.id = errorId;
                field.setAttribute('aria-describedby', errorId);
            }
            
            this.errors[fieldName] = message;
        }
        
        clearFieldError(field) {
            const errorContainer = field.parentNode.querySelector('.field-error');
            if (errorContainer) {
                errorContainer.remove();
            }
            
            field.classList.remove('border-red-500');
            field.removeAttribute('aria-invalid');
            field.removeAttribute('aria-describedby');
            
            delete this.errors[field.name];
        }
        
        addFormError(message) {
            let formErrorContainer = this.form.querySelector('.form-error');
            if (!formErrorContainer) {
                formErrorContainer = document.createElement('div');
                formErrorContainer.className = 'form-error bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4';
                formErrorContainer.setAttribute('role', 'alert');
                this.form.insertBefore(formErrorContainer, this.form.firstChild);
            }
            
            formErrorContainer.textContent = message;
        }
        
        clearFormErrors() {
            const formErrorContainer = this.form.querySelector('.form-error');
            if (formErrorContainer) {
                formErrorContainer.remove();
            }
        }
        
        showServerErrors(errors) {
            // Server-Fehler anzeigen
            for (const [fieldName, messages] of Object.entries(errors)) {
                const field = this.form.querySelector(`[name="${fieldName}"]`);
                if (field && messages.length > 0) {
                    this.addFieldError(field, messages[0]);
                }
            }
        }
    }
    
    // Utility-Funktionen
    window.FormValidationUtils = {
        // Passwort-Stärke prüfen
        validatePasswordStrength: (password) => {
            if (password.length < 8) return 'Passwort muss mindestens 8 Zeichen lang sein';
            if (!/[A-Z]/.test(password)) return 'Passwort muss mindestens einen Großbuchstaben enthalten';
            if (!/[a-z]/.test(password)) return 'Passwort muss mindestens einen Kleinbuchstaben enthalten';
            if (!/[0-9]/.test(password)) return 'Passwort muss mindestens eine Zahl enthalten';
            return true;
        },
        
        // Passwort-Bestätigung prüfen
        validatePasswordConfirm: (password, confirm) => {
            return password === confirm ? true : 'Passwörter stimmen nicht überein';
        },
        
        // Datei-Validierung
        validateFile: (file, allowedTypes = [], maxSizeMB = 10) => {
            if (!file) return 'Bitte wählen Sie eine Datei aus';
            
            const fileType = file.name.split('.').pop().toLowerCase();
            if (allowedTypes.length > 0 && !allowedTypes.includes(fileType)) {
                return `Nur folgende Dateiformate sind erlaubt: ${allowedTypes.join(', ')}`;
            }
            
            if (file.size > maxSizeMB * 1024 * 1024) {
                return `Datei ist zu groß. Maximum: ${maxSizeMB} MB`;
            }
            
            return true;
        }
    };
    """

def render_validation_errors(errors: Dict[str, List[str]]) -> str:
    """Rendert Validierungsfehler als HTML"""
    if not errors:
        return ""
    
    html_parts = ['<div class="validation-errors">']
    
    for field, messages in errors.items():
        for message in messages:
            html_parts.append(
                f'<div class="error-message bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-2" role="alert">'
                f'<strong>{field}:</strong> {html.escape(message)}'
                f'</div>'
            )
    
    html_parts.append('</div>')
    
    return '\n'.join(html_parts) 