import uuid
from typing import Optional, Dict, Any
from datetime import datetime


class BaseApplicationException(Exception):
    def __init__(
        self,
        message: str,
        code: str,
        status_code: int = 500,
        details: Optional[Dict[str, Any]] = None,
        user_message: Optional[str] = None
    ):
        super().__init__(message)
        self.message = message
        self.code = code
        self.status_code = status_code
        self.details = details or {}
        self.user_message = user_message or message
        self.timestamp = datetime.utcnow()
        self.request_id = str(uuid.uuid4())
    
    def to_dict(self, include_details: bool = False) -> Dict[str, Any]:
        result = {
            'error': {
                'code': self.code,
                'message': self.user_message,
                'timestamp': self.timestamp.isoformat(),
                'request_id': self.request_id
            }
        }
        
        if include_details and self.details:
            result['error']['details'] = self.details
            
        return result


class ValidationException(BaseApplicationException):
    def __init__(
        self,
        message: str,
        field: Optional[str] = None,
        value: Any = None,
        details: Optional[Dict[str, Any]] = None,
        user_message: Optional[str] = None
    ):
        details = details or {}
        if field:
            details['field'] = field
        if value is not None:
            details['value'] = str(value)
            
        super().__init__(
            message=message,
            code='VALIDATION_ERROR',
            status_code=400,
            details=details,
            user_message=user_message or "Ungültige Eingabe"
        )


class InputValidationError(ValidationException):
    def __init__(
        self,
        field: str,
        message: str,
        value: Any = None,
        expected_type: Optional[str] = None
    ):
        details = {'expected_type': expected_type} if expected_type else None
        super().__init__(
            message=f"Invalid input for field '{field}': {message}",
            field=field,
            value=value,
            details=details,
            user_message=f"Ungültiger Wert für Feld '{field}'"
        )


class BusinessRuleViolation(ValidationException):
    def __init__(
        self,
        rule: str,
        message: str,
        context: Optional[Dict[str, Any]] = None
    ):
        super().__init__(
            message=message,
            details={'rule': rule, 'context': context or {}},
            user_message="Geschäftsregel verletzt"
        )


class DataIntegrityError(ValidationException):
    def __init__(
        self,
        entity: str,
        constraint: str,
        message: str,
        details: Optional[Dict[str, Any]] = None
    ):
        details = details or {}
        details.update({'entity': entity, 'constraint': constraint})
        super().__init__(
            message=message,
            details=details,
            user_message="Datenintegritätsfehler"
        )


class AuthenticationException(BaseApplicationException):
    def __init__(
        self,
        message: str,
        details: Optional[Dict[str, Any]] = None,
        user_message: Optional[str] = None
    ):
        super().__init__(
            message=message,
            code='AUTHENTICATION_ERROR',
            status_code=401,
            details=details,
            user_message=user_message or "Authentifizierung fehlgeschlagen"
        )


class InvalidCredentialsError(AuthenticationException):
    def __init__(self, username: Optional[str] = None):
        details = {'username': username} if username else None
        super().__init__(
            message="Invalid username or password",
            details=details,
            user_message="Ungültiger Benutzername oder Passwort"
        )


class SessionExpiredError(AuthenticationException):
    def __init__(self, session_id: Optional[str] = None):
        details = {'session_id': session_id} if session_id else None
        super().__init__(
            message="Session has expired",
            details=details,
            user_message="Ihre Sitzung ist abgelaufen"
        )


class InsufficientPermissionsError(AuthenticationException):
    def __init__(
        self,
        required_permission: str,
        user_permissions: Optional[list] = None
    ):
        super().__init__(
            message=f"User lacks required permission: {required_permission}",
            details={
                'required': required_permission,
                'user_permissions': user_permissions or []
            },
            user_message="Unzureichende Berechtigungen für diese Aktion"
        )
        self.status_code = 403


class DatabaseException(BaseApplicationException):
    def __init__(
        self,
        message: str,
        query: Optional[str] = None,
        details: Optional[Dict[str, Any]] = None,
        user_message: Optional[str] = None
    ):
        details = details or {}
        if query:
            details['query_hash'] = str(hash(query))
            
        super().__init__(
            message=message,
            code='DATABASE_ERROR',
            status_code=500,
            details=details,
            user_message=user_message or "Datenbankfehler aufgetreten"
        )


class ConnectionError(DatabaseException):
    def __init__(self, message: str, host: Optional[str] = None):
        details = {'host': host} if host else None
        super().__init__(
            message=message,
            details=details,
            user_message="Datenbankverbindung fehlgeschlagen"
        )


class QueryError(DatabaseException):
    def __init__(
        self,
        message: str,
        query: str,
        error_code: Optional[str] = None
    ):
        super().__init__(
            message=message,
            query=query,
            details={'error_code': error_code} if error_code else None,
            user_message="Datenbankabfrage fehlgeschlagen"
        )


class TransactionError(DatabaseException):
    def __init__(self, message: str, operation: str):
        super().__init__(
            message=message,
            details={'operation': operation},
            user_message="Transaktion fehlgeschlagen"
        )


class ExternalServiceException(BaseApplicationException):
    def __init__(
        self,
        service_name: str,
        message: str,
        details: Optional[Dict[str, Any]] = None,
        user_message: Optional[str] = None
    ):
        details = details or {}
        details['service'] = service_name
        
        super().__init__(
            message=message,
            code='EXTERNAL_SERVICE_ERROR',
            status_code=502,
            details=details,
            user_message=user_message or f"Fehler beim Zugriff auf {service_name}"
        )


class APIError(ExternalServiceException):
    def __init__(
        self,
        service_name: str,
        endpoint: str,
        status_code: int,
        message: str
    ):
        super().__init__(
            service_name=service_name,
            message=message,
            details={
                'endpoint': endpoint,
                'response_status': status_code
            },
            user_message=f"API-Fehler bei {service_name}"
        )


class TimeoutError(ExternalServiceException):
    def __init__(
        self,
        service_name: str,
        timeout_seconds: int,
        operation: str
    ):
        super().__init__(
            service_name=service_name,
            message=f"Timeout after {timeout_seconds}s while {operation}",
            details={
                'timeout_seconds': timeout_seconds,
                'operation': operation
            },
            user_message=f"Zeitüberschreitung bei {service_name}"
        )


class ResourceException(BaseApplicationException):
    def __init__(
        self,
        message: str,
        resource_type: str,
        resource_id: Any = None,
        details: Optional[Dict[str, Any]] = None,
        user_message: Optional[str] = None
    ):
        details = details or {}
        details.update({
            'resource_type': resource_type,
            'resource_id': str(resource_id) if resource_id else None
        })
        
        super().__init__(
            message=message,
            code='RESOURCE_ERROR',
            status_code=404,
            details=details,
            user_message=user_message or "Ressourcenfehler"
        )


class ResourceNotFoundError(ResourceException):
    def __init__(
        self,
        resource_type: str,
        resource_id: Any = None,
        search_criteria: Optional[Dict[str, Any]] = None
    ):
        details = {'search_criteria': search_criteria} if search_criteria else None
        super().__init__(
            message=f"{resource_type} not found",
            resource_type=resource_type,
            resource_id=resource_id,
            details=details,
            user_message=f"{resource_type} nicht gefunden"
        )
        self.status_code = 404


class ResourceConflictError(ResourceException):
    def __init__(
        self,
        resource_type: str,
        resource_id: Any,
        conflict_reason: str
    ):
        super().__init__(
            message=f"Conflict with {resource_type}: {conflict_reason}",
            resource_type=resource_type,
            resource_id=resource_id,
            details={'conflict_reason': conflict_reason},
            user_message=f"Konflikt mit {resource_type}"
        )
        self.status_code = 409


class ResourceLimitExceeded(ResourceException):
    def __init__(
        self,
        resource_type: str,
        limit: int,
        current: int,
        requested: Optional[int] = None
    ):
        details = {
            'limit': limit,
            'current': current,
            'requested': requested
        }
        super().__init__(
            message=f"{resource_type} limit exceeded: {current}/{limit}",
            resource_type=resource_type,
            details=details,
            user_message=f"Limit für {resource_type} überschritten"
        )
        self.status_code = 429