"""
🎬 Django сигналы для django-hlsfield

Автоматическая обработка событий жизненного цикла видео:
- Создание директорий после миграций
- Очистка файлов при удалении объектов
- Автоматические уведомления
- Обновление статистики
- Интеграция с внешними системами

Автор: akula993
Лицензия: MIT
"""

import logging
import os
import time
from pathlib import Path
from typing import List, Optional

from django.conf import settings
from django.core.cache import cache
from django.db.models.signals import post_migrate, post_save, post_delete, pre_save, pre_delete
from django.dispatch import receiver
from django.utils import timezone

from .apps import HLSFieldConfig
from .helpers import get_model_video_fields

logger = logging.getLogger(__name__)


# ==============================================================================
# СИГНАЛЫ МИГРАЦИЙ И ИНИЦИАЛИЗАЦИИ
# ==============================================================================


@receiver(post_migrate, sender=HLSFieldConfig)
def create_media_directories(sender, **kwargs):
    """
    Создает необходимые media директории после миграций.

    Создает базовую структуру папок для видео файлов,
    превью, HLS и DASH контента.
    """
    if not hasattr(settings, "MEDIA_ROOT") or not settings.MEDIA_ROOT:
        logger.debug("MEDIA_ROOT not configured, skipping directory creation")
        return

    media_root = Path(settings.MEDIA_ROOT)

    # Базовые директории для видео
    directories = [
        media_root / "videos",
        media_root / "videos" / "hls",
        media_root / "videos" / "dash",
        media_root / "videos" / "adaptive",
        media_root / "videos" / "previews",
        media_root / "videos" / "temp",  # Для временных файлов
    ]

    # Дополнительные директории из настроек
    extra_dirs = getattr(settings, "HLSFIELD_EXTRA_DIRECTORIES", [])
    for extra_dir in extra_dirs:
        directories.append(media_root / extra_dir)

    created = []
    failed = []

    for directory in directories:
        try:
            if not directory.exists():
                directory.mkdir(parents=True, exist_ok=True)
                created.append(str(directory))

                # Устанавливаем правильные разрешения
                try:
                    os.chmod(directory, 0o755)
                except OSError:
                    pass  # Игнорируем если не можем изменить права

        except Exception as e:
            failed.append(f"{directory}: {e}")
            logger.warning(f"Could not create directory {directory}: {e}")

    if created:
        logger.info(f"Created media directories: {', '.join(created)}")

    if failed:
        logger.warning(f"Failed to create directories: {', '.join(failed)}")


@receiver(post_migrate)
def cleanup_old_migrations_cache(sender, **kwargs):
    """Очищает кеш после миграций для корректной работы"""

    # Очищаем кеш настроек
    cache_keys = [
        "hlsfield_settings_validated",
        "ffmpeg_availability_checked",
        "video_analytics_*",
    ]

    for pattern in cache_keys:
        if "*" in pattern:
            # Очищаем по паттерну (если поддерживается)
            try:
                cache.delete_many(cache.keys(pattern.replace("*", "*")))
            except (AttributeError, NotImplementedError):
                pass
        else:
            cache.delete(pattern)

    logger.debug("Cleared hlsfield caches after migration")


# ==============================================================================
# СИГНАЛЫ ЖИЗНЕННОГО ЦИКЛА ВИДЕО ОБЪЕКТОВ
# ==============================================================================


@receiver(pre_save)
def video_field_pre_save_handler(sender, instance, **kwargs):
    """
    Обработка перед сохранением объектов с video полями.

    - Подготавливает метаданные
    - Проверяет доступность storage
    - Устанавливает начальные статусы
    """

    # Получаем все video поля модели
    video_fields = get_model_video_fields(sender)

    if not video_fields:
        return  # В модели нет video полей

    for field_name in video_fields:
        field_file = getattr(instance, field_name)

        if not field_file or not field_file.name:
            continue

        # Устанавливаем начальный статус обработки
        if hasattr(instance, "processing_status") and not getattr(
            instance, "processing_status", None
        ):
            setattr(instance, "processing_status", "pending")

        # Устанавливаем время загрузки
        if hasattr(instance, "video_uploaded_at") and not getattr(
            instance, "video_uploaded_at", None
        ):
            setattr(instance, "video_uploaded_at", timezone.now())

        # Генерируем уникальный ID если нужно
        if hasattr(instance, "video_id") and not getattr(instance, "video_id", None):
            from .helpers import generate_video_id

            setattr(instance, "video_id", generate_video_id())


@receiver(post_save)
def video_field_post_save_handler(sender, instance, created, **kwargs):
    """
    Обработка после сохранения объектов с video полями.

    - Обновляет статистику
    - Создает связанные объекты
    - Отправляет уведомления
    """

    video_fields = get_model_video_fields(sender)

    if not video_fields:
        return

    for field_name in video_fields:
        field_file = getattr(instance, field_name)

        if not field_file or not field_file.name:
            continue

        # Обновляем счетчик видео в приложении
        if created:
            _increment_video_counter(sender.__name__)

        # Создаем запись в аналитике если включена
        if _is_analytics_enabled():
            _create_video_analytics_record(
                instance, field_name, "uploaded" if created else "updated"
            )

        # Отправляем уведомление о загрузке
        if created and _should_send_notifications():
            _send_video_upload_notification(instance, field_name)


@receiver(pre_delete)
def video_field_pre_delete_handler(sender, instance, **kwargs):
    """
    Обработка перед удалением объектов с video полями.

    - Сохраняет информацию для аналитики
    - Подготавливает список файлов для удаления
    """

    video_fields = get_model_video_fields(sender)

    if not video_fields:
        return

    # Сохраняем информацию о видео для аналитики
    video_info = {}

    for field_name in video_fields:
        field_file = getattr(instance, field_name)

        if field_file and field_file.name:
            video_info[field_name] = {
                "name": field_file.name,
                "size": _get_file_size_safe(field_file),
                "url": _get_file_url_safe(field_file),
            }

            # Добавляем связанные файлы (HLS, DASH, превью)
            video_info[field_name]["related_files"] = _get_related_files(instance, field_name)

    # Сохраняем во временном атрибуте для post_delete
    setattr(instance, "_hlsfield_deletion_info", video_info)


@receiver(post_delete)
def video_field_post_delete_handler(sender, instance, **kwargs):
    """
    Очистка файлов после удаления объектов с video полями.

    - Удаляет видео файлы из storage
    - Очищает связанные HLS/DASH файлы
    - Обновляет статистику
    """

    # Получаем сохраненную информацию
    deletion_info = getattr(instance, "_hlsfield_deletion_info", {})

    if not deletion_info:
        return

    deleted_files = []
    failed_deletions = []

    for field_name, file_info in deletion_info.items():
        try:
            # Удаляем основной файл
            main_file = file_info["name"]
            if main_file and _delete_file_safe(main_file):
                deleted_files.append(main_file)

            # Удаляем связанные файлы
            related_files = file_info.get("related_files", [])
            for related_file in related_files:
                if _delete_file_safe(related_file):
                    deleted_files.append(related_file)
                else:
                    failed_deletions.append(related_file)

        except Exception as e:
            logger.error(
                f"Error deleting files for {sender.__name__}:{instance.pk}.{field_name}: {e}"
            )
            failed_deletions.append(f"{field_name}: {e}")

    # Логируем результаты
    if deleted_files:
        logger.info(f"Deleted {len(deleted_files)} video files for {sender.__name__}:{instance.pk}")

    if failed_deletions:
        logger.warning(f"Failed to delete {len(failed_deletions)} files: {failed_deletions}")

    # Обновляем статистику
    _decrement_video_counter(sender.__name__)

    # Аналитика удаления
    if _is_analytics_enabled():
        _create_video_analytics_record(instance, None, "deleted", extra_data=deletion_info)


# ==============================================================================
# СИГНАЛЫ ДЛЯ УВЕДОМЛЕНИЙ И ИНТЕГРАЦИЙ
# ==============================================================================


@receiver(post_save)
def video_processing_status_changed(sender, instance, created, **kwargs):
    """
    Отслеживает изменения статуса обработки видео.

    Отправляет уведомления когда видео готово к просмотру.
    """

    if not hasattr(instance, "processing_status"):
        return

    # Проверяем изменился ли статус на "готово"
    if not created:
        try:
            # Получаем предыдущую версию из БД
            old_instance = sender.objects.get(pk=instance.pk)
            old_status = getattr(old_instance, "processing_status", None)
            new_status = getattr(instance, "processing_status", None)

            # Если статус изменился на готовность
            if old_status != new_status and new_status in [
                "ready",
                "hls_ready",
                "dash_ready",
                "adaptive_ready",
            ]:
                _handle_video_ready_notification(instance, new_status)

        except sender.DoesNotExist:
            pass  # Объект был создан только что
        except Exception as e:
            logger.warning(f"Error checking processing status change: {e}")


@receiver(post_save)
def update_video_statistics(sender, instance, created, **kwargs):
    """
    Обновляет статистику видео при изменениях.

    Ведет счетчики загрузок, обработки, размеров файлов.
    """

    video_fields = get_model_video_fields(sender)

    if not video_fields:
        return

    # Обновляем общую статистику приложения
    try:
        _update_app_statistics(sender, instance, created)
    except Exception as e:
        logger.debug(f"Failed to update statistics: {e}")


# ==============================================================================
# ИНТЕГРАЦИОННЫЕ СИГНАЛЫ
# ==============================================================================


@receiver(post_save)
def integrate_with_search_engines(sender, instance, created, **kwargs):
    """
    Интеграция с поисковыми системами при добавлении видео.

    Отправляет данные в Elasticsearch, Solr и т.д.
    """

    if not _is_search_integration_enabled():
        return

    video_fields = get_model_video_fields(sender)

    if not video_fields:
        return

    try:
        # Подготавливаем данные для индексации
        search_data = _prepare_search_data(instance, video_fields)

        if search_data:
            _send_to_search_engine(search_data, action="index" if created else "update")

    except Exception as e:
        logger.warning(f"Search engine integration failed: {e}")


@receiver(post_delete)
def remove_from_search_engines(sender, instance, **kwargs):
    """Удаляет видео из поисковых индексов"""

    if not _is_search_integration_enabled():
        return

    video_fields = get_model_video_fields(sender)

    if video_fields:
        try:
            _send_to_search_engine({"id": instance.pk}, action="delete")
        except Exception as e:
            logger.warning(f"Search engine removal failed: {e}")


@receiver(post_save)
def trigger_cdn_purge(sender, instance, created, **kwargs):
    """
    Очищает CDN кеш при обновлении видео.

    Интегрируется с CloudFlare, CloudFront, KeyCDN и другими.
    """

    if not _is_cdn_integration_enabled():
        return

    video_fields = get_model_video_fields(sender)

    if not video_fields:
        return

    urls_to_purge = []

    for field_name in video_fields:
        field_file = getattr(instance, field_name)

        if field_file and field_file.name:
            # Собираем URL для очистки
            urls_to_purge.extend(_get_cdn_urls_for_purge(instance, field_name))

    if urls_to_purge:
        try:
            _purge_cdn_cache(urls_to_purge)
            logger.info(f"Purged {len(urls_to_purge)} URLs from CDN cache")
        except Exception as e:
            logger.warning(f"CDN cache purge failed: {e}")


# ==============================================================================
# ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ
# ==============================================================================


def _increment_video_counter(model_name: str):
    """Увеличивает счетчик видео для модели"""
    cache_key = f"hlsfield_video_count_{model_name}"

    try:
        current = cache.get(cache_key, 0)
        cache.set(cache_key, current + 1, 86400)  # 24 часа
    except Exception:
        pass


def _decrement_video_counter(model_name: str):
    """Уменьшает счетчик видео для модели"""
    cache_key = f"hlsfield_video_count_{model_name}"

    try:
        current = cache.get(cache_key, 0)
        cache.set(cache_key, max(0, current - 1), 86400)
    except Exception:
        pass


def _get_file_size_safe(field_file) -> Optional[int]:
    """Безопасно получает размер файла"""
    try:
        return field_file.size
    except Exception:
        return None


def _get_file_url_safe(field_file) -> Optional[str]:
    """Безопасно получает URL файла"""
    try:
        return field_file.url
    except Exception:
        return None


def _get_related_files(instance, field_name: str) -> List[str]:
    """Получает список связанных файлов (HLS, DASH, превью)"""
    related_files = []

    field = instance._meta.get_field(field_name)

    # HLS плейлист
    hls_field = getattr(field, "hls_playlist_field", None)
    if hls_field:
        hls_path = getattr(instance, hls_field, None)
        if hls_path:
            related_files.append(hls_path)
            # Добавляем предположительные HLS сегменты
            related_files.extend(_get_hls_segments(hls_path))

    # DASH манифест
    dash_field = getattr(field, "dash_manifest_field", None)
    if dash_field:
        dash_path = getattr(instance, dash_field, None)
        if dash_path:
            related_files.append(dash_path)
            # Добавляем предположительные DASH сегменты
            related_files.extend(_get_dash_segments(dash_path))

    # Превью
    preview_field = getattr(field, "preview_field", None)
    if preview_field:
        preview_path = getattr(instance, preview_field, None)
        if preview_path:
            related_files.append(preview_path)

    return related_files


def _get_hls_segments(playlist_path: str) -> List[str]:
    """Получает список HLS сегментов по пути к плейлисту"""
    segments = []

    try:
        # Предполагаем структуру: path/to/hls/master.m3u8
        base_dir = str(Path(playlist_path).parent)

        # Добавляем типичные варианты качества
        for quality in ["v240", "v360", "v480", "v720", "v1080"]:
            segments.append(f"{base_dir}/{quality}/index.m3u8")

            # Сегменты (примерно 5-20 файлов)
            for i in range(20):
                segments.append(f"{base_dir}/{quality}/seg_{i:04d}.ts")

    except Exception:
        pass

    return segments


def _get_dash_segments(manifest_path: str) -> List[str]:
    """Получает список DASH сегментов по пути к манифесту"""
    segments = []

    try:
        base_dir = str(Path(manifest_path).parent)

        # DASH init и media сегменты
        for i in range(5):  # Примерно 5 представлений
            segments.append(f"{base_dir}/init-{i}.m4s")

            # Media сегменты
            for j in range(20):
                segments.append(f"{base_dir}/chunk-{i}-{j:05d}.m4s")

    except Exception:
        pass

    return segments


def _delete_file_safe(file_path: str) -> bool:
    """Безопасно удаляет файл из storage"""
    try:
        from django.core.files.storage import default_storage

        if default_storage.exists(file_path):
            default_storage.delete(file_path)
            return True
    except Exception as e:
        logger.debug(f"Could not delete file {file_path}: {e}")

    return False


def _is_analytics_enabled() -> bool:
    """Проверяет включена ли аналитика"""
    return getattr(settings, "HLSFIELD_ENABLE_ANALYTICS", False)


def _should_send_notifications() -> bool:
    """Проверяет нужно ли отправлять уведомления"""
    return getattr(settings, "HLSFIELD_SEND_NOTIFICATIONS", False)


def _is_search_integration_enabled() -> bool:
    """Проверяет включена ли интеграция с поиском"""
    return getattr(settings, "HLSFIELD_SEARCH_INTEGRATION", False)


def _is_cdn_integration_enabled() -> bool:
    """Проверяет включена ли интеграция с CDN"""
    return getattr(settings, "HLSFIELD_CDN_INTEGRATION", False)


def _create_video_analytics_record(
    instance, field_name: Optional[str], action: str, extra_data: dict = None
):
    """Создает запись в аналитике"""
    try:
        from .views import VideoEvent

        VideoEvent.objects.create(
            video_id=str(instance.pk),
            session_id="system",
            event_type=action,
            timestamp=timezone.now(),
            additional_data=extra_data or {},
        )
    except Exception as e:
        logger.debug(f"Could not create analytics record: {e}")


def _send_video_upload_notification(instance, field_name: str):
    """Отправляет уведомление о загрузке видео"""
    try:
        # Пример интеграции с Django channels или email
        notification_data = {
            "type": "video_uploaded",
            "instance_id": instance.pk,
            "model": instance._meta.label,
            "field_name": field_name,
            "timestamp": timezone.now().isoformat(),
        }

        # Здесь можно добавить отправку в Slack, Discord, email и т.д.
        logger.info(f"Video uploaded notification: {notification_data}")

    except Exception as e:
        logger.warning(f"Failed to send upload notification: {e}")


def _handle_video_ready_notification(instance, status: str):
    """Обрабатывает уведомления о готовности видео"""
    try:
        # Можно отправить email пользователю
        if hasattr(instance, "user") and instance.user:
            _send_video_ready_email(instance.user, instance, status)

        # Уведомление в админку
        _send_admin_notification(f"Video {instance.pk} is {status}")

    except Exception as e:
        logger.warning(f"Failed to send ready notification: {e}")


def _send_video_ready_email(user, instance, status: str):
    """Отправляет email о готовности видео"""
    try:
        from django.core.mail import send_mail
        from django.template.loader import render_to_string

        subject = f"Your video is ready!"
        message = render_to_string(
            "hlsfield/emails/video_ready.html",
            {
                "user": user,
                "instance": instance,
                "status": status,
            },
        )

        send_mail(
            subject=subject,
            message=message,
            from_email=settings.DEFAULT_FROM_EMAIL,
            recipient_list=[user.email],
            fail_silently=True,
        )

    except Exception as e:
        logger.debug(f"Could not send video ready email: {e}")


def _send_admin_notification(message: str):
    """Отправляет уведомление администраторам"""
    try:
        from django.core.mail import mail_admins

        mail_admins(
            subject="HLSField Notification",
            message=message,
            fail_silently=True,
        )
    except Exception:
        pass


def _update_app_statistics(sender, instance, created: bool):
    """Обновляет статистику приложения"""
    stats_key = "hlsfield_app_stats"

    try:
        stats = cache.get(
            stats_key,
            {
                "total_videos": 0,
                "total_processed": 0,
                "last_update": time.time(),
            },
        )

        if created:
            stats["total_videos"] += 1

        if hasattr(instance, "processing_status"):
            status = getattr(instance, "processing_status")
            if status in ["ready", "hls_ready", "dash_ready", "adaptive_ready"]:
                stats["total_processed"] += 1

        stats["last_update"] = time.time()
        cache.set(stats_key, stats, 86400)  # 24 часа

    except Exception:
        pass


def _prepare_search_data(instance, video_fields: List[str]) -> dict:
    """Подготавливает данные для поисковых систем"""
    search_data = {
        "id": instance.pk,
        "model": instance._meta.label,
        "timestamp": timezone.now().isoformat(),
    }

    # Добавляем основную информацию
    for field in ["title", "description", "tags"]:
        if hasattr(instance, field):
            search_data[field] = getattr(instance, field)

    # Добавляем информацию о видео
    for field_name in video_fields:
        try:
            from .helpers import get_video_field_metadata

            metadata = get_video_field_metadata(instance, field_name)
            search_data[f"{field_name}_metadata"] = metadata
        except Exception:
            pass

    return search_data


def _send_to_search_engine(data: dict, action: str):
    """Отправляет данные в поисковую систему"""
    # Заглушка для интеграции с Elasticsearch и т.д.
    logger.debug(f"Search engine {action}: {data}")


def _get_cdn_urls_for_purge(instance, field_name: str) -> List[str]:
    """Получает URL для очистки CDN кеша"""
    urls = []

    try:
        from .helpers import get_video_field_metadata

        metadata = get_video_field_metadata(instance, field_name)

        # Добавляем основные URL
        for url_key in ["url", "hls_url", "dash_url", "preview_url"]:
            if url_key in metadata:
                urls.append(metadata[url_key])

    except Exception:
        pass

    return urls


def _purge_cdn_cache(urls: List[str]):
    """Очищает кеш CDN для указанных URL"""
    # Заглушка для интеграции с CloudFlare, CloudFront и т.д.
    logger.debug(f"CDN purge: {urls}")


# ==============================================================================
# УСЛОВНЫЕ СИГНАЛЫ (активируются через настройки)
# ==============================================================================

# Дополнительные сигналы можно активировать через настройки
if getattr(settings, "HLSFIELD_ENABLE_WEBHOOKS", False):

    @receiver(post_save)
    def send_webhooks(sender, instance, created, **kwargs):
        """Отправляет webhook уведомления"""
        video_fields = get_model_video_fields(sender)

        if video_fields:
            webhook_url = getattr(settings, "HLSFIELD_WEBHOOK_URL", None)
            if webhook_url:
                try:
                    import requests

                    payload = {
                        "action": "created" if created else "updated",
                        "model": sender.__name__,
                        "instance_id": instance.pk,
                        "timestamp": timezone.now().isoformat(),
                    }

                    requests.post(webhook_url, json=payload, timeout=5)

                except Exception as e:
                    logger.warning(f"Webhook failed: {e}")


# ==============================================================================
# СИГНАЛ ДЛЯ ОЧИСТКИ КЕША
# ==============================================================================


@receiver([post_save, post_delete])
def invalidate_related_caches(sender, instance, **kwargs):
    """Очищает связанные кеши при изменении объектов с видео"""

    video_fields = get_model_video_fields(sender)

    if not video_fields:
        return

    # Очищаем кеши связанные с этим объектом
    cache_patterns = [
        f"video_metadata_{instance.pk}",
        f"video_stats_{instance.pk}",
        f"video_analytics_{instance.pk}",
        f"hlsfield_video_count_{sender.__name__}",
    ]

    for pattern in cache_patterns:
        try:
            cache.delete(pattern)
        except Exception:
            pass


# ==============================================================================
# ДОКУМЕНТАЦИЯ И НАСТРОЙКИ
# ==============================================================================

"""
Настройки сигналов в settings.py:

HLSFIELD_ENABLE_ANALYTICS = True          # Включить аналитику
HLSFIELD_SEND_NOTIFICATIONS = True        # Отправлять уведомления
HLSFIELD_SEARCH_INTEGRATION = True        # Интеграция с поиском
HLSFIELD_CDN_INTEGRATION = True           # Интеграция с CDN
HLSFIELD_ENABLE_WEBHOOKS = True           # Webhook уведомления
HLSFIELD_WEBHOOK_URL = "https://..."      # URL для webhook

HLSFIELD_EXTRA_DIRECTORIES = [            # Дополнительные директории
    'videos/backup',
    'videos/temp',
]

# Email настройки для уведомлений
DEFAULT_FROM_EMAIL = 'noreply@example.com'
ADMINS = [('Admin', 'admin@example.com')]
"""
