"""
🚨 Пользовательские исключения для django-hlsfield

Иерархия исключений для детального handling различных типов ошибок
при работе с видео обработкой, транскодингом и storage операциями.

Все исключения наследуют от HLSFieldError для удобного catch-all handling.

Автор: django-hlsfield team
Лицензия: MIT
"""

from typing import List, Optional, Any, Dict


# ==============================================================================
# БАЗОВОЕ ИСКЛЮЧЕНИЕ
# ==============================================================================

class HLSFieldError(Exception):
    """
    Базовое исключение для всех ошибок django-hlsfield.

    Используется как catch-all для всех ошибок пакета.
    Содержит дополнительную информацию для debugging и monitoring.
    """

    def __init__(self, message: str, details: Optional[Dict[str, Any]] = None,
                 suggestions: Optional[List[str]] = None):
        """
        Args:
            message: Основное сообщение об ошибке
            details: Дополнительная техническая информация
            suggestions: Список возможных решений проблемы
        """
        super().__init__(message)
        self.message = message
        self.details = details or {}
        self.suggestions = suggestions or []

    def __str__(self) -> str:
        result = self.message

        if self.details:
            result += f" (Details: {self.details})"

        if self.suggestions:
            suggestions_str = "; ".join(self.suggestions)
            result += f" (Suggestions: {suggestions_str})"

        return result

    def to_dict(self) -> Dict[str, Any]:
        """Возвращает структурированное представление ошибки"""
        return {
            'error_type': self.__class__.__name__,
            'message': self.message,
            'details': self.details,
            'suggestions': self.suggestions
        }


# ==============================================================================
# ОШИБКИ FFMPEG
# ==============================================================================

class FFmpegNotFoundError(HLSFieldError):
    """
    FFmpeg или FFprobe не найдены в системе.

    Критическая ошибка конфигурации - без FFmpeg пакет не может работать.
    """

    def __init__(self, binary_name: str = "ffmpeg"):
        self.binary_name = binary_name

        message = f"{binary_name} not found in system PATH"

        suggestions = [
            "Install FFmpeg: https://ffmpeg.org/download.html",
            f"Set HLSFIELD_FFMPEG and HLSFIELD_FFPROBE in Django settings",
            "Verify FFmpeg installation with: ffmpeg -version",
            "Check system PATH includes FFmpeg directory"
        ]

        details = {
            'binary_name': binary_name,
            'error_category': 'system_configuration'
        }

        super().__init__(message, details, suggestions)


class FFmpegError(HLSFieldError):
    """
    Ошибка выполнения команды FFmpeg.

    Содержит детальную информацию о команде, коде возврата и output.
    """

    def __init__(self, command: List[str], returncode: int,
                 stdout: str = "", stderr: str = ""):

        self.command = command
        self.returncode = returncode
        self.stdout = stdout
        self.stderr = stderr

        command_str = ' '.join(command)
        message = f"FFmpeg command failed with code {returncode}: {command_str}"

        details = {
            'command': command,
            'returncode': returncode,
            'stdout': stdout[:500],  # Ограничиваем для readability
            'stderr': stderr[:500],
            'error_category': 'ffmpeg_execution'
        }

        suggestions = self._generate_suggestions()

        super().__init__(message, details, suggestions)

    def _generate_suggestions(self) -> List[str]:
        """Генерирует suggestions на основе stderr"""
        suggestions = []

        stderr_lower = self.stderr.lower()

        if "no such file" in stderr_lower:
            suggestions.extend([
                "Check input file path exists and is readable",
                "Verify file permissions",
                "Use absolute file path"
            ])

        elif "invalid data found" in stderr_lower:
            suggestions.extend([
                "Input file may be corrupted",
                "Try with a different video file",
                "Check file was fully uploaded"
            ])

        elif "permission denied" in stderr_lower:
            suggestions.extend([
                "Check file and directory permissions",
                "Run with appropriate user privileges",
                "Verify storage write permissions"
            ])

        elif "no space left" in stderr_lower:
            suggestions.extend([
                "Free up disk space",
                "Check temporary directory has sufficient space",
                "Consider using different storage location"
            ])

        elif "unknown encoder" in stderr_lower:
            suggestions.extend([
                "Check FFmpeg was compiled with required encoders",
                "Install FFmpeg with libx264 support",
                "Update FFmpeg to newer version"
            ])

        if not suggestions:
            suggestions.append("Check FFmpeg documentation for this error")

        return suggestions


class FFmpegTimeoutError(FFmpegError):
    """FFmpeg команда превысила таймаут"""

    def __init__(self, command: List[str], timeout_seconds: int):
        self.timeout_seconds = timeout_seconds

        message = f"FFmpeg command timed out after {timeout_seconds} seconds"

        super().__init__(command, -1, "", f"Timeout after {timeout_seconds}s")

        self.suggestions = [
            f"Increase timeout (current: {timeout_seconds}s)",
            "Check if input video is very long or high resolution",
            "Consider using faster FFmpeg preset",
            "Monitor system resources during transcoding"
        ]


# ==============================================================================
# ОШИБКИ ВИДЕОФАЙЛОВ
# ==============================================================================

class InvalidVideoError(HLSFieldError):
    """
    Недопустимый, поврежденный или неподдерживаемый видеофайл.
    """

    def __init__(self, message: str, file_path: Optional[str] = None,
                 file_info: Optional[Dict[str, Any]] = None):

        self.file_path = file_path
        self.file_info = file_info or {}

        details = {
            'file_path': file_path,
            'file_info': file_info,
            'error_category': 'video_validation'
        }

        suggestions = [
            "Try with a different video file",
            "Check file was fully uploaded and not truncated",
            "Verify file format is supported (MP4, AVI, MOV, etc.)",
            "Use FFmpeg to validate file: ffprobe your_file.mp4"
        ]

        super().__init__(message, details, suggestions)


class UnsupportedFormatError(InvalidVideoError):
    """Неподдерживаемый формат видеофайла"""

    def __init__(self, format_name: str, file_path: Optional[str] = None):
        self.format_name = format_name

        message = f"Unsupported video format: {format_name}"

        file_info = {'detected_format': format_name}

        super().__init__(message, file_path, file_info)

        self.suggestions = [
            "Convert video to supported format (MP4, MOV, AVI)",
            "Use FFmpeg to convert: ffmpeg -i input.{format_name} output.mp4",
            "Check HLSFIELD_ALLOWED_EXTENSIONS setting",
            f"Current format '{format_name}' not in allowed list"
        ]


class VideoTooLargeError(InvalidVideoError):
    """Видеофайл превышает максимальный размер"""

    def __init__(self, file_size: int, max_size: int, file_path: Optional[str] = None):
        self.file_size = file_size
        self.max_size = max_size

        size_mb = file_size / (1024 * 1024)
        max_mb = max_size / (1024 * 1024)

        message = f"Video file too large: {size_mb:.1f}MB (max: {max_mb:.1f}MB)"

        file_info = {
            'size_bytes': file_size,
            'size_mb': size_mb,
            'max_size_bytes': max_size,
            'max_size_mb': max_mb
        }

        super().__init__(message, file_path, file_info)

        self.suggestions = [
            f"Compress video to under {max_mb:.0f}MB",
            "Reduce video bitrate or resolution",
            "Split video into smaller segments",
            "Increase HLSFIELD_MAX_FILE_SIZE setting if needed"
        ]


class VideoTooShortError(InvalidVideoError):
    """Видео слишком короткое для обработки"""

    def __init__(self, duration: float, min_duration: float = 1.0):
        self.duration = duration
        self.min_duration = min_duration

        message = f"Video too short: {duration:.1f}s (minimum: {min_duration:.1f}s)"

        file_info = {
            'duration_seconds': duration,
            'min_duration_seconds': min_duration
        }

        super().__init__(message, None, file_info)


# ==============================================================================
# ОШИБКИ ТРАНСКОДИНГА
# ==============================================================================

class TranscodingError(HLSFieldError):
    """
    Общие ошибки процесса транскодинга видео.
    """

    def __init__(self, message: str, stage: Optional[str] = None,
                 original_error: Optional[Exception] = None):

        self.stage = stage
        self.original_error = original_error

        details = {
            'transcoding_stage': stage,
            'original_error': str(original_error) if original_error else None,
            'error_category': 'transcoding'
        }

        suggestions = [
            "Check system has sufficient CPU and memory",
            "Verify temporary storage has enough space",
            "Try with a simpler quality ladder",
            "Check FFmpeg logs for detailed error info"
        ]

        if stage:
            suggestions.insert(0, f"Error occurred during {stage} stage")

        super().__init__(message, details, suggestions)


class HLSTranscodingError(TranscodingError):
    """Ошибки при создании HLS стрима"""

    def __init__(self, message: str, variant_height: Optional[int] = None,
                 segments_created: int = 0):

        self.variant_height = variant_height
        self.segments_created = segments_created

        details = {
            'variant_height': variant_height,
            'segments_created': segments_created,
            'transcoding_type': 'HLS'
        }

        suggestions = [
            "Check HLS segment duration is reasonable (2-10 seconds)",
            "Verify output directory is writable",
            "Try with fewer quality variants",
            "Check source video is not corrupted"
        ]

        if variant_height:
            suggestions.insert(0, f"Error creating {variant_height}p HLS variant")

        super().__init__(message, "HLS_transcoding", None)
        self.details.update(details)


class DASHTranscodingError(TranscodingError):
    """Ошибки при создании DASH стрима"""

    def __init__(self, message: str, representations_created: int = 0):
        self.representations_created = representations_created

        details = {
            'representations_created': representations_created,
            'transcoding_type': 'DASH'
        }

        suggestions = [
            "Check DASH segment settings are valid",
            "Verify manifest template configuration",
            "Try shorter segment duration (2-4 seconds)",
            "Check FFmpeg DASH support is available"
        ]

        super().__init__(message, "DASH_transcoding", None)
        self.details.update(details)


# ==============================================================================
# ОШИБКИ STORAGE
# ==============================================================================

class StorageError(HLSFieldError):
    """
    Ошибки при работе с файловым хранилищем.
    """

    def __init__(self, message: str, operation: Optional[str] = None,
                 file_path: Optional[str] = None, storage_backend: Optional[str] = None):

        self.operation = operation
        self.file_path = file_path
        self.storage_backend = storage_backend

        details = {
            'operation': operation,
            'file_path': file_path,
            'storage_backend': storage_backend,
            'error_category': 'storage'
        }

        suggestions = [
            "Check file and directory permissions",
            "Verify storage backend configuration",
            "Ensure sufficient storage space available"
        ]

        if operation == 'upload':
            suggestions.extend([
                "Check network connectivity for cloud storage",
                "Verify authentication credentials",
                "Try uploading smaller test file"
            ])
        elif operation == 'download':
            suggestions.extend([
                "Verify file exists in storage",
                "Check read permissions",
                "Confirm storage backend is accessible"
            ])

        super().__init__(message, details, suggestions)


class S3StorageError(StorageError):
    """Специфичные ошибки AWS S3 storage"""

    def __init__(self, message: str, bucket: Optional[str] = None,
                 key: Optional[str] = None, aws_error_code: Optional[str] = None):

        self.bucket = bucket
        self.key = key
        self.aws_error_code = aws_error_code

        details = {
            'bucket': bucket,
            'key': key,
            'aws_error_code': aws_error_code,
            'storage_type': 'S3'
        }

        suggestions = [
            "Check AWS credentials (ACCESS_KEY_ID, SECRET_ACCESS_KEY)",
            "Verify S3 bucket exists and is accessible",
            "Check S3 bucket permissions and policies",
            "Confirm AWS region is correct"
        ]

        if aws_error_code == 'NoSuchBucket':
            suggestions.insert(0, f"S3 bucket '{bucket}' does not exist")
        elif aws_error_code == 'AccessDenied':
            suggestions.insert(0, "S3 access denied - check IAM permissions")
        elif aws_error_code == 'NoSuchKey':
            suggestions.insert(0, f"S3 object '{key}' not found")

        super().__init__(message, None, key, 'S3')
        self.details.update(details)


# ==============================================================================
# ОШИБКИ КОНФИГУРАЦИИ
# ==============================================================================

class ConfigurationError(HLSFieldError):
    """
    Ошибки конфигурации django-hlsfield.
    """

    def __init__(self, message: str, setting_name: Optional[str] = None,
                 current_value: Optional[Any] = None, expected_type: Optional[str] = None):

        self.setting_name = setting_name
        self.current_value = current_value
        self.expected_type = expected_type

        details = {
            'setting_name': setting_name,
            'current_value': current_value,
            'expected_type': expected_type,
            'error_category': 'configuration'
        }

        suggestions = [
            "Check Django settings.py configuration",
            "Verify HLSFIELD_* settings are correct",
            "Run: python manage.py hlsfield_health_check",
            "Review django-hlsfield documentation"
        ]

        if setting_name:
            suggestions.insert(0, f"Fix {setting_name} setting in Django configuration")

        super().__init__(message, details, suggestions)


class InvalidLadderError(ConfigurationError):
    """Некорректная конфигурация лестницы качеств"""

    def __init__(self, message: str, ladder: Optional[List[Dict]] = None,
                 rung_index: Optional[int] = None):

        self.ladder = ladder
        self.rung_index = rung_index

        details = {
            'ladder_length': len(ladder) if ladder else 0,
            'invalid_rung_index': rung_index,
            'setting_type': 'quality_ladder'
        }

        suggestions = [
            "Check ladder format: [{'height': 720, 'v_bitrate': 2500, 'a_bitrate': 128}, ...]",
            "Verify all ladder rungs have required fields",
            "Ensure bitrates and heights are positive integers",
            "Remove duplicate height values from ladder"
        ]

        if rung_index is not None:
            suggestions.insert(0, f"Fix ladder rung at index {rung_index}")

        super().__init__(message, "ladder", ladder, "List[Dict]")
        self.details.update(details)


# ==============================================================================
# ОШИБКИ CELERY/ЗАДАЧ
# ==============================================================================

class TaskError(HLSFieldError):
    """
    Ошибки выполнения фоновых задач.
    """

    def __init__(self, message: str, task_name: Optional[str] = None,
                 task_id: Optional[str] = None, retry_count: int = 0):

        self.task_name = task_name
        self.task_id = task_id
        self.retry_count = retry_count

        details = {
            'task_name': task_name,
            'task_id': task_id,
            'retry_count': retry_count,
            'error_category': 'task_execution'
        }

        suggestions = [
            "Check Celery worker is running",
            "Verify Redis/broker connectivity",
            "Monitor system resources (CPU, memory, disk)",
            "Check task logs for detailed errors"
        ]

        if retry_count > 0:
            suggestions.insert(0, f"Task failed after {retry_count} retries")

        super().__init__(message, details, suggestions)


class CeleryNotAvailableError(TaskError):
    """Celery не доступен, fallback на синхронную обработку"""

    def __init__(self):
        message = "Celery not available, falling back to synchronous processing"

        suggestions = [
            "Install Celery: pip install celery",
            "Configure Celery in Django settings",
            "Start Celery worker: celery -A myproject worker",
            "Synchronous processing will continue but may be slow"
        ]

        details = {'fallback_mode': 'synchronous'}

        super().__init__(message, None, None, 0)
        self.details.update(details)


# ==============================================================================
# ОШИБКИ ВАЛИДАЦИИ
# ==============================================================================

class ValidationError(HLSFieldError):
    """
    Ошибки валидации входных данных.
    """

    def __init__(self, message: str, field_name: Optional[str] = None,
                 validation_errors: Optional[List[str]] = None):

        self.field_name = field_name
        self.validation_errors = validation_errors or []

        details = {
            'field_name': field_name,
            'validation_errors': validation_errors,
            'error_category': 'validation'
        }

        suggestions = []
        if validation_errors:
            suggestions.extend(validation_errors)

        suggestions.append("Check input data format and constraints")

        super().__init__(message, details, suggestions)


# ==============================================================================
# NETWORK И TIMEOUT ОШИБКИ
# ==============================================================================

class TimeoutError(HLSFieldError):
    """Операция превысила таймаут"""

    def __init__(self, message: str, timeout_seconds: int, operation: Optional[str] = None):
        self.timeout_seconds = timeout_seconds
        self.operation = operation

        details = {
            'timeout_seconds': timeout_seconds,
            'operation': operation,
            'error_category': 'timeout'
        }

        suggestions = [
            f"Increase timeout (current: {timeout_seconds}s)",
            "Check if operation is appropriate for timeout duration",
            "Monitor system performance during operation"
        ]

        if operation:
            suggestions.insert(0, f"Timeout during {operation}")

        super().__init__(message, details, suggestions)


class NetworkError(HLSFieldError):
    """Сетевые ошибки при работе с удаленными ресурсами"""

    def __init__(self, message: str, url: Optional[str] = None,
                 status_code: Optional[int] = None):
        self.url = url
        self.status_code = status_code

        details = {
            'url': url,
            'status_code': status_code,
            'error_category': 'network'
        }

        suggestions = [
            "Check network connectivity",
            "Verify URL is accessible",
            "Check firewall and proxy settings"
        ]

        if status_code:
            if status_code == 404:
                suggestions.insert(0, "Resource not found (404)")
            elif status_code == 403:
                suggestions.insert(0, "Access forbidden (403) - check permissions")
            elif status_code >= 500:
                suggestions.insert(0, f"Server error ({status_code}) - try again later")

        super().__init__(message, details, suggestions)


# ==============================================================================
# УТИЛИТЫ ДЛЯ РАБОТЫ С ИСКЛЮЧЕНИЯМИ
# ==============================================================================

def categorize_exception(error: Exception) -> Dict[str, Any]:
    """
    Анализирует исключение и возвращает структурированную информацию.

    Args:
        error: Исключение для анализа

    Returns:
        dict: Категоризированная информация об ошибке
    """

    if isinstance(error, HLSFieldError):
        return error.to_dict()

    # Для стандартных исключений Python
    error_info = {
        'error_type': error.__class__.__name__,
        'message': str(error),
        'details': {},
        'suggestions': []
    }

    # Специальная обработка для некоторых стандартных исключений
    if isinstance(error, FileNotFoundError):
        error_info['details']['error_category'] = 'file_system'
        error_info['suggestions'] = [
            "Check file path exists",
            "Verify file permissions",
            "Ensure file was not moved or deleted"
        ]

    elif isinstance(error, PermissionError):
        error_info['details']['error_category'] = 'permissions'
        error_info['suggestions'] = [
            "Check file and directory permissions",
            "Run with appropriate user privileges",
            "Verify write access to destination"
        ]

    elif isinstance(error, OSError):
        error_info['details']['error_category'] = 'system'
        error_info['suggestions'] = [
            "Check system resources (disk space, memory)",
            "Verify system configuration",
            "Check for hardware issues"
        ]

    return error_info


def format_exception_for_user(error: Exception) -> str:
    """
    Форматирует исключение для показа пользователю (без технических деталей).

    Args:
        error: Исключение для форматирования

    Returns:
        str: Пользовательское сообщение об ошибке
    """

    if isinstance(error, HLSFieldError):
        message = error.message

        if error.suggestions:
            # Берем только первые 2 suggestion для краткости
            suggestions = error.suggestions[:2]
            message += f"\n\nПопробуйте: {'; '.join(suggestions)}"

        return message

    # Для стандартных исключений
    error_messages = {
        FileNotFoundError: "Файл не найден. Проверьте путь к файлу.",
        PermissionError: "Недостаточно прав доступа. Проверьте разрешения.",
        OSError: "Системная ошибка. Проверьте ресурсы системы.",
        ValueError: "Некорректное значение. Проверьте входные данные.",
        TypeError: "Неправильный тип данных. Проверьте формат данных."
    }

    return error_messages.get(type(error), f"Произошла ошибка: {str(error)}")


def is_retryable_error(error: Exception) -> bool:
    """
    Определяет можно ли повторить операцию после данной ошибки.

    Args:
        error: Исключение для анализа

    Returns:
        bool: True если ошибка временная и можно повторить
    """

    # Ошибки которые НЕ стоит повторять
    non_retryable = (
        FFmpegNotFoundError,
        InvalidVideoError,
        UnsupportedFormatError,
        VideoTooLargeError,
        ConfigurationError,
        ValidationError
    )

    if isinstance(error, non_retryable):
        return False

    # Ошибки которые можно повторить
    retryable = (
        NetworkError,
        TimeoutError,
        StorageError,
        TaskError
    )

    if isinstance(error, retryable):
        return True

    # Для HLSFieldError проверяем категорию
    if isinstance(error, HLSFieldError):
        error_category = error.details.get('error_category')

        non_retryable_categories = [
            'video_validation',
            'configuration',
            'system_configuration'
        ]

        return error_category not in non_retryable_categories

    # Стандартные исключения Python
    if isinstance(error, (ConnectionError, TimeoutError)):
        return True

    if isinstance(error, (ValueError, TypeError, FileNotFoundError)):
        return False

    # По умолчанию не повторяем
    return False


# ==============================================================================
# ЭКСПОРТ ИСКЛЮЧЕНИЙ
# ==============================================================================

__all__ = [
    # Базовые
    'HLSFieldError',

    # FFmpeg
    'FFmpegNotFoundError',
    'FFmpegError',
    'FFmpegTimeoutError',

    # Видео
    'InvalidVideoError',
    'UnsupportedFormatError',
    'VideoTooLargeError',
    'VideoTooShortError',

    # Транскодинг
    'TranscodingError',
    'HLSTranscodingError',
    'DASHTranscodingError',

    # Storage
    'StorageError',
    'S3StorageError',

    # Конфигурация
    'ConfigurationError',
    'InvalidLadderError',

    # Задачи
    'TaskError',
    'CeleryNotAvailableError',

    # Валидация
    'ValidationError',

    # Network
    'TimeoutError',
    'NetworkError',

    # Утилиты
    'categorize_exception',
    'format_exception_for_user',
    'is_retryable_error',
]
