"""
Утилита для обработки и изменения размера изображений товаров, комплектов и категорий.
Автоматически создает несколько версий (original, thumbnail, medium, large) при сохранении.

Конфигурация берется из settings.IMAGE_PROCESSING_CONFIG
"""
import os
import logging
from io import BytesIO
from PIL import Image
from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
from django.conf import settings

logger = logging.getLogger(__name__)


class ImageProcessor:
    """
    Обработчик изображений с поддержкой создания нескольких размеров и форматов.
    Использует конфигурацию из settings.IMAGE_PROCESSING_CONFIG

    МУЛЬТИТЕНАНТНОСТЬ:
    На диске файлы хранятся как: tenants/{tenant_id}/products/{entity_id}/{photo_id}/{size}.ext
    В БД сохраняется путь БЕЗ tenant_id: products/{entity_id}/{photo_id}/{size}.ext
    TenantAwareFileSystemStorage автоматически добавляет tenant_id при сохранении файлов.
    """

    # Константы для маппинга форматов и расширений файлов
    FORMAT_EXTENSIONS = {
        'JPEG': 'jpg',
        'WEBP': 'webp',
        'PNG': 'png',
    }

    # Размеры для обработки
    SIZE_KEYS = ['original', 'large', 'medium', 'thumbnail']

    @staticmethod
    def _get_config():
        """Получить конфигурацию из settings"""
        return getattr(settings, 'IMAGE_PROCESSING_CONFIG', {})

    @staticmethod
    def _get_size_dimensions(size_key):
        """Получить размеры для заданного типа изображения"""
        config = ImageProcessor._get_config()
        formats = config.get('formats', {})
        size_config = formats.get(size_key, {})

        # Для оригинала используем max_width/max_height
        if size_key == 'original':
            return (size_config.get('max_width', 2160), size_config.get('max_height', 2160))
        else:
            return (size_config.get('width', 400), size_config.get('height', 400))

    @staticmethod
    def _get_format_config(size_key):
        """Получить конфигурацию формата для заданного типа изображения"""
        config = ImageProcessor._get_config()
        formats = config.get('formats', {})
        return formats.get(size_key, {'format': 'JPEG', 'quality': 90})

    @staticmethod
    def _get_file_extension(image_format):
        """Получить расширение файла для заданного формата"""
        return ImageProcessor.FORMAT_EXTENSIONS.get(image_format, 'jpg')

    @staticmethod
    def _normalize_size_name(size_key):
        """Преобразовать 'thumbnail' в 'thumb' для имени файла"""
        return 'thumb' if size_key == 'thumbnail' else size_key


    @staticmethod
    def process_image(image_file, base_path, entity_id=None, photo_id=None):
        """
        Обрабатывает загруженное изображение и создает несколько версий.

        Args:
            image_file: Загруженный файл изображения (InMemoryUploadedFile)
            base_path: Базовый путь для сохранения (например, 'products', 'kits', 'categories')
            entity_id: ID сущности (product_id, category_id, kit_id)
            photo_id: ID фотографии

        Returns:
            dict: Словарь с сохраненными данными
                {
                    'original': 'products/<entity_id>/<photo_id>/original.jpg',
                    'large': 'products/<entity_id>/<photo_id>/large.webp',
                    'medium': 'products/<entity_id>/<photo_id>/medium.webp',
                    'thumbnail': 'products/<entity_id>/<photo_id>/thumb.webp',
                    'width': 1920,
                    'height': 1080,
                    'quality_level': 'good',
                    'quality_warning': False,
                }

        Raises:
            ValueError: Если файл не является изображением или отсутствуют обязательные параметры
        """
        if entity_id is None or photo_id is None:
            raise ValueError("entity_id and photo_id are required parameters")

        try:
            # Открываем изображение
            img = Image.open(image_file)
            width, height = img.size

            # Определяем качество на основе размеров
            from ..validators.image_validators import get_image_quality_level
            quality_level, needs_update = get_image_quality_level(width, height)

            # Конвертируем в RGB если необходимо (для JPEG/WebP)
            if img.mode in ('RGBA', 'LA', 'P'):
                # Создаем белый фон для прозрачных областей
                background = Image.new('RGB', img.size, (255, 255, 255))
                if img.mode == 'P':
                    img = img.convert('RGBA')
                background.paste(img, mask=img.split()[-1] if img.mode == 'RGBA' else None)
                img = background
            elif img.mode != 'RGB':
                img = img.convert('RGB')

            saved_paths = {}

            # Сохраняем оригинал (масштабируем если больше max_width/max_height)
            original_path = ImageProcessor._save_image_version(
                img, base_path, entity_id, photo_id, 'original'
            )
            saved_paths['original'] = original_path

            # Создаем и сохраняем остальные размеры
            for size_key in ['large', 'medium', 'thumbnail']:
                size_dims = ImageProcessor._get_size_dimensions(size_key)
                resized_img = ImageProcessor._resize_image(img, size_dims)

                size_path = ImageProcessor._save_image_version(
                    resized_img, base_path, entity_id, photo_id, size_key
                )
                saved_paths[size_key] = size_path

            # Добавляем информацию о качестве
            saved_paths['width'] = width
            saved_paths['height'] = height
            saved_paths['quality_level'] = quality_level
            saved_paths['quality_warning'] = needs_update

            logger.info(
                f"Image processed: {width}x{height}px → quality={quality_level}, warning={needs_update}"
            )

            return saved_paths

        except Exception as e:
            raise ValueError(f"Не удалось обработать изображение: {str(e)}")

    @staticmethod
    def _resize_image(img, size):
        """
        Изменяет размер изображения с center-crop до точного квадратного размера.
        НЕ увеличивает маленькие изображения (сохраняет качество).
        Создает квадратное изображение без белых полей.

        Args:
            img: PIL Image object
            size: Кортеж (width, height) - целевой размер (обычно квадратный)

        Returns:
            PIL Image object - квадратное изображение без белых полей
        """
        img_copy = img.copy()
        target_width, target_height = size

        # Шаг 1: Center crop для получения квадрата
        # Определяем минимальную сторону (будет размер квадрата)
        min_side = min(img_copy.width, img_copy.height)

        # Вычисляем координаты для обрезки из центра
        left = (img_copy.width - min_side) // 2
        top = (img_copy.height - min_side) // 2
        right = left + min_side
        bottom = top + min_side

        # Обрезаем до квадрата
        img_cropped = img_copy.crop((left, top, right, bottom))

        # Шаг 2: Масштабируем до целевого размера (если исходный квадрат больше цели)
        # Не увеличиваем маленькие изображения
        if min_side > target_width:
            img_resized = img_cropped.resize((target_width, target_height), Image.Resampling.LANCZOS)
        else:
            img_resized = img_cropped

        return img_resized

    @staticmethod
    def _make_square_image(img, max_size):
        """
        Делает изображение квадратным с белым фоном.

        Args:
            img: PIL Image object
            max_size: Максимальный размер стороны квадрата

        Returns:
            PIL Image object - квадратное изображение
        """
        # Если изображение больше max_size, масштабируем
        if img.width > max_size or img.height > max_size:
            scale_factor = min(max_size / img.width, max_size / img.height)
            new_width = int(img.width * scale_factor)
            new_height = int(img.height * scale_factor)
            img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)

        # Создаем квадратное изображение
        size_obj = min(max(img.width, img.height), max_size)
        square_img = Image.new('RGB', (size_obj, size_obj), (255, 255, 255))

        # Центрируем изображение
        offset_x = (size_obj - img.width) // 2
        offset_y = (size_obj - img.height) // 2
        square_img.paste(img, (offset_x, offset_y))

        return square_img

    @staticmethod
    def _save_image_version(img, base_path, entity_id, photo_id, size_key):
        """
        Сохраняет версию изображения с фиксированными именами.
        Использует формат и качество из конфигурации для каждого размера.

        Args:
            img: PIL Image object
            base_path: Базовый путь (например, 'products')
            entity_id: ID сущности (product_id, category_id, kit_id)
            photo_id: ID фотографии
            size_key: Ключ размера ('original', 'large', 'medium', 'thumbnail')

        Returns:
            str: Путь сохраненного файла относительно MEDIA_ROOT
        """
        # Получаем конфигурацию для этого размера
        format_config = ImageProcessor._get_format_config(size_key)
        image_format = format_config.get('format', 'JPEG')
        quality = format_config.get('quality', 90)

        # Определяем расширение и имя файла
        extension = ImageProcessor._get_file_extension(image_format)
        final_size_name = ImageProcessor._normalize_size_name(size_key)
        filename = f"{final_size_name}.{extension}"

        # Создаем путь: base_path/entity_id/photo_id/filename
        file_path = f"{base_path}/{entity_id}/{photo_id}/{filename}"

        # ВАЖНО: Удаляем старый файл если он существует, чтобы избежать коллизий имен
        if default_storage.exists(file_path):
            try:
                default_storage.delete(file_path)
                logger.info(f"Deleted old file: {file_path}")
            except Exception as e:
                logger.warning(f"Could not delete old file {file_path}: {str(e)}")

        # Обрабатываем оригинал - делаем квадратным
        if size_key == 'original':
            max_size = ImageProcessor._get_size_dimensions('original')[0]
            img = ImageProcessor._make_square_image(img, max_size)

        # Сохраняем в памяти
        img_io = BytesIO()
        save_kwargs = {'format': image_format, 'optimize': True}

        if image_format in ('JPEG', 'WEBP'):
            save_kwargs['quality'] = quality

        img.save(img_io, **save_kwargs)
        img_io.seek(0)

        # Сохраняем в хранилище
        # ВАЖНО: используем name=file_path чтобы принудительно использовать это имя
        saved_path = default_storage.save(file_path, ContentFile(img_io.getvalue()))

        # Если Django добавил суффикс (коллизия имен), логируем это
        if saved_path != file_path:
            logger.warning(f"File collision detected: expected {file_path}, got {saved_path}")

        logger.info(f"Saved {image_format} image: {saved_path} (quality: {quality})")
        return saved_path

    @staticmethod
    def _cleanup_empty_directory(dir_path):
        """
        Удаляет директорию, если она пуста. Используется для очистки после удаления файлов.

        Args:
            dir_path: Путь к директории относительно MEDIA_ROOT (без tenant_id префикса)

        Returns:
            bool: True если директория была удалена, False иначе
        """
        if not dir_path:
            return False

        try:
            # Получаем полный путь с учетом tenant_id (TenantAwareFileSystemStorage добавляет его)
            full_path = default_storage.path(dir_path)

            # Проверяем что путь существует и это директория
            if os.path.isdir(full_path):
                # Проверяем что директория пуста
                if not os.listdir(full_path):
                    os.rmdir(full_path)
                    logger.info(f"Removed empty directory: {dir_path}")
                    return True
                else:
                    logger.debug(f"Directory not empty, skipping: {dir_path}")
                    return False
        except Exception as e:
            logger.warning(f"Could not remove directory {dir_path}: {str(e)}")
            return False

    @staticmethod
    def delete_all_versions(base_path, original_image_path, entity_id=None, photo_id=None):
        """
        Удаляет все версии изображения (original, large, medium, thumb).
        После удаления всех файлов удаляет пустую директорию photo_id.

        Args:
            base_path: Базовый путь (например, 'products')
            original_image_path: Путь к оригинальному файлу (из БД)
            entity_id: ID сущности (product_id, category_id, kit_id)
            photo_id: ID фотографии
        """
        if not original_image_path:
            return

        if entity_id is None or photo_id is None:
            logger.error("entity_id and photo_id are required for deleting images")
            return

        # Удаляем все размеры
        for size_key in ImageProcessor.SIZE_KEYS:
            format_config = ImageProcessor._get_format_config(size_key)
            image_format = format_config.get('format', 'JPEG')

            # Определяем расширение и имя файла
            extension = ImageProcessor._get_file_extension(image_format)
            final_size_name = ImageProcessor._normalize_size_name(size_key)
            size_filename = f"{final_size_name}.{extension}"

            # Создаем путь
            file_path = f"{base_path}/{entity_id}/{photo_id}/{size_filename}"

            try:
                if default_storage.exists(file_path):
                    default_storage.delete(file_path)
                    logger.info(f"Deleted file: {file_path}")
                else:
                    logger.warning(f"File not found: {file_path}")
            except Exception as e:
                logger.error(f"Error deleting {file_path}: {str(e)}", exc_info=True)

        # Очищаем пустую директорию фотографии (photo_id)
        photo_dir = f"{base_path}/{entity_id}/{photo_id}"
        ImageProcessor._cleanup_empty_directory(photo_dir)
