from decimal import Decimal
from django.db import models
from django.utils import timezone


class DiscountCombiner:
    """
    Утилитный класс для комбинирования скидок по разным режимам.
    """

    @staticmethod
    def combine_discounts(discounts, base_amount):
        """
        Комбинировать несколько скидок по их combine_mode.

        Args:
            discounts: Список объектов Discount
            base_amount: Базовая сумма для расчета

        Returns:
            dict: {
                'total_discount': Decimal,
                'applied_discounts': [{'discount': Discount, 'amount': Decimal}, ...],
                'excluded_by': Discount или None,
                'combine_mode_used': str
            }
        """
        if not discounts:
            return {
                'total_discount': Decimal('0'),
                'applied_discounts': [],
                'excluded_by': None,
                'combine_mode_used': 'none'
            }

        # 1. Проверяем наличие exclusive скидки
        exclusive_discounts = [d for d in discounts if d.combine_mode == 'exclusive']
        if exclusive_discounts:
            # Применяем только первую exclusive скидку
            discount = exclusive_discounts[0]
            amount = discount.calculate_discount_amount(base_amount)
            return {
                'total_discount': amount,
                'applied_discounts': [{'discount': discount, 'amount': amount}],
                'excluded_by': discount,
                'combine_mode_used': 'exclusive'
            }

        # 2. Разделяем скидки на stack и max_only
        stack_discounts = [d for d in discounts if d.combine_mode == 'stack']
        max_only_discounts = [d for d in discounts if d.combine_mode == 'max_only']

        result = {
            'total_discount': Decimal('0'),
            'applied_discounts': [],
            'excluded_by': None,
            'combine_mode_used': 'combined'
        }

        # 3. Для max_only применяем только максимальную
        if max_only_discounts:
            max_discount = max(
                max_only_discounts,
                key=lambda d: d.calculate_discount_amount(base_amount)
            )
            amount = max_discount.calculate_discount_amount(base_amount)
            result['applied_discounts'].append({'discount': max_discount, 'amount': amount})
            result['total_discount'] += amount

        # 4. Для stack суммируем все
        for discount in stack_discounts:
            amount = discount.calculate_discount_amount(base_amount)
            result['applied_discounts'].append({'discount': discount, 'amount': amount})
            result['total_discount'] += amount

        # 5. Ограничиваем итоговую скидку базовой суммой
        result['total_discount'] = min(result['total_discount'], base_amount)

        return result


class DiscountCalculator:
    """
    Калькулятор скидок для заказов.
    Рассчитывает применимые скидки и их суммы.
    """

    @staticmethod
    def get_available_discounts(scope=None, customer=None, auto_only=False):
        """
        Получить список доступных скидок.

        Args:
            scope: 'order', 'product', 'category' или None для всех
            customer: Customer для проверки условий
            auto_only: Только автоматические скидки

        Returns:
            QuerySet[Discount]: Активные скидки, отсортированные по приоритету
        """
        from discounts.models import Discount

        now = timezone.now()
        qs = Discount.objects.filter(is_active=True)

        # Фильтр по scope
        if scope:
            qs = qs.filter(scope=scope)

        # Фильтр по auto
        if auto_only:
            qs = qs.filter(is_auto=True)

        # Фильтр по дате
        qs = qs.filter(
            models.Q(start_date__isnull=True) | models.Q(start_date__lte=now),
            models.Q(end_date__isnull=True) | models.Q(end_date__gte=now)
        )

        # Фильтр по лимиту использований
        qs = qs.filter(
            models.Q(max_usage_count__isnull=True) |
            models.Q(current_usage_count__lt=models.F('max_usage_count'))
        )

        return qs.order_by('-priority', '-created_at')

    @staticmethod
    def calculate_order_discount(order, promo_code=None):
        """
        Рассчитать скидку на весь заказ с поддержкой комбинирования.

        Args:
            order: Order объект
            promo_code: Строка промокода (опционально)

        Returns:
            dict: {
                'discounts': [{'discount': Discount, 'amount': Decimal}, ...],
                'total_amount': Decimal,
                'promo_code': PromoCode или None,
                'error': str или None
            }
        """
        from discounts.models import PromoCode
        from discounts.services.validator import DiscountValidator

        subtotal = Decimal(str(order.subtotal))
        result = {
            'discounts': [],
            'total_amount': Decimal('0'),
            'promo_code': None,
            'error': None
        }

        applicable_discounts = []

        # 1. Проверяем промокод первым
        if promo_code:
            is_valid, promo, error = DiscountValidator.validate_promo_code(
                promo_code, order.customer, subtotal
            )

            if not is_valid:
                result['error'] = error
                return result

            discount = promo.discount

            if discount.scope != 'order':
                result['error'] = "Этот промокод применяется только к товарам"
                return result

            result['promo_code'] = promo
            applicable_discounts.append(discount)
        else:
            # 2. Если нет промокода, собираем все автоматические скидки
            auto_discounts = DiscountCalculator.get_available_discounts(
                scope='order',
                auto_only=True
            )

            for discount in auto_discounts:
                if discount.min_order_amount and subtotal < discount.min_order_amount:
                    continue
                applicable_discounts.append(discount)

        # 3. Комбинируем скидки
        if applicable_discounts:
            combination = DiscountCombiner.combine_discounts(applicable_discounts, subtotal)
            result['discounts'] = combination['applied_discounts']
            result['total_amount'] = combination['total_discount']

        return result

    @staticmethod
    def calculate_item_discount(order_item, available_discounts=None):
        """
        Рассчитать скидку на позицию заказа с поддержкой комбинирования.

        Args:
            order_item: OrderItem объект
            available_discounts: Предварительно полученный список скидок

        Returns:
            dict: {
                'discounts': [{'discount': Discount, 'amount': Decimal}, ...],
                'total_amount': Decimal,
            }
        """
        result = {
            'discounts': [],
            'total_amount': Decimal('0')
        }

        # Определяем продукт
        product = None
        if order_item.product:
            product = order_item.product
        elif order_item.product_kit:
            product = order_item.product_kit

        if not product:
            return result

        base_amount = Decimal(str(order_item.price)) * Decimal(str(order_item.quantity))

        # Собираем все применимые скидки
        applicable_discounts = []

        # Скидки на товары
        if not available_discounts:
            available_discounts = DiscountCalculator.get_available_discounts(
                scope='product',
                auto_only=True
            )

        for discount in available_discounts:
            if discount.applies_to_product(product):
                applicable_discounts.append(discount)

        # Скидки по категориям
        category_discounts = DiscountCalculator.get_available_discounts(
            scope='category',
            auto_only=True
        )

        for discount in category_discounts:
            if discount.applies_to_product(product) and discount not in applicable_discounts:
                applicable_discounts.append(discount)

        # Комбинируем скидки
        if applicable_discounts:
            combination = DiscountCombiner.combine_discounts(applicable_discounts, base_amount)
            result['discounts'] = combination['applied_discounts']
            result['total_amount'] = combination['total_discount']

        return result

    @staticmethod
    def calculate_cart_discounts(cart_items, promo_code=None, customer=None, skip_auto_discount=False):
        """
        Рассчитать скидки для корзины (применяется в POS до создания заказа).
        Поддерживает комбинирование скидок по combine_mode.

        Args:
            cart_items: Список словарей {'type': 'product'|'kit', 'id': int, 'quantity': Decimal, 'price': Decimal}
            promo_code: Промокод (опционально)
            customer: Customer (опционально)
            skip_auto_discount: Пропустить автоматические скидки (опционально)

        Returns:
            dict: {
                'order_discounts': [
                    {'discount_id': int, 'discount_name': str, 'discount_amount': Decimal, 'combine_mode': str},
                    ...
                ],
                'total_order_discount': Decimal,
                'item_discounts': [
                    {'cart_index': int, 'discounts': [...], 'total_discount': Decimal},
                    ...
                ],
                'total_discount': Decimal,
                'final_total': Decimal,
                'cart_subtotal': Decimal,
                'excluded_by': {'id': int, 'name': str} или None
            }
        """
        from products.models import Product, ProductKit

        cart_subtotal = Decimal('0')
        for item in cart_items:
            cart_subtotal += Decimal(str(item['price'])) * Decimal(str(item['quantity']))

        # Если нужно пропустить авто-скидки, возвращаем пустой результат
        if skip_auto_discount:
            return {
                'order_discounts': [],
                'total_order_discount': Decimal('0'),
                'item_discounts': [],
                'total_discount': Decimal('0'),
                'final_total': cart_subtotal,
                'cart_subtotal': cart_subtotal,
                'excluded_by': None
            }

        # Создаем фейковый объект для расчета скидки на заказ
        class FakeOrder:
            def __init__(self, subtotal, customer):
                self.subtotal = subtotal
                self.customer = customer

        fake_order = FakeOrder(cart_subtotal, customer)

        # Скидка на заказ (с комбинированием)
        order_result = DiscountCalculator.calculate_order_discount(fake_order, promo_code)

        # Форматируем order_discounts для ответа
        formatted_order_discounts = []
        excluded_by = None

        for disc_data in order_result['discounts']:
            discount = disc_data['discount']
            formatted_order_discounts.append({
                'discount_id': discount.id,
                'discount_name': discount.name,
                'discount_amount': disc_data['amount'],
                'discount_type': discount.discount_type,
                'discount_value': discount.value,
                'combine_mode': discount.combine_mode
            })

        # Проверяем исключающую скидку
        if order_result['discounts']:
            # Определяем exclusive скидку
            for disc_data in order_result['discounts']:
                if disc_data['discount'].combine_mode == 'exclusive':
                    excluded_by = {
                        'id': disc_data['discount'].id,
                        'name': disc_data['discount'].name
                    }
                    break

        # Скидки на позиции (с комбинированием)
        item_discounts = []
        items_total_discount = Decimal('0')

        available_product_discounts = list(DiscountCalculator.get_available_discounts(
            scope='product',
            auto_only=True
        ))

        available_category_discounts = list(DiscountCalculator.get_available_discounts(
            scope='category',
            auto_only=True
        ))

        for idx, item in enumerate(cart_items):
            # Загружаем продукт
            product = None
            if item.get('type') == 'product':
                try:
                    product = Product.objects.get(id=item['id'])
                except Product.DoesNotExist:
                    pass
            elif item.get('type') == 'kit':
                try:
                    product = ProductKit.objects.get(id=item['id'])
                except ProductKit.DoesNotExist:
                    pass

            if not product:
                continue

            base_amount = Decimal(str(item['price'])) * Decimal(str(item['quantity']))

            # Собираем все применимые скидки для этого товара
            applicable_item_discounts = []

            for discount in available_product_discounts:
                if discount.applies_to_product(product):
                    applicable_item_discounts.append(discount)

            for discount in available_category_discounts:
                if discount.applies_to_product(product) and discount not in applicable_item_discounts:
                    applicable_item_discounts.append(discount)

            # Комбинируем скидки для позиции
            if applicable_item_discounts:
                combination = DiscountCombiner.combine_discounts(
                    applicable_item_discounts,
                    base_amount
                )

                formatted_discounts = []
                for disc_data in combination['applied_discounts']:
                    discount = disc_data['discount']
                    formatted_discounts.append({
                        'discount_id': discount.id,
                        'discount_name': discount.name,
                        'discount_amount': disc_data['amount'],
                        'combine_mode': discount.combine_mode
                    })

                if formatted_discounts:
                    item_discounts.append({
                        'cart_index': idx,
                        'discounts': formatted_discounts,
                        'total_discount': combination['total_discount']
                    })
                    items_total_discount += combination['total_discount']

        total_discount = order_result['total_amount'] + items_total_discount
        final_total = max(cart_subtotal - total_discount, Decimal('0'))

        return {
            'order_discounts': formatted_order_discounts,
            'total_order_discount': order_result['total_amount'],
            'item_discounts': item_discounts,
            'total_discount': total_discount,
            'final_total': final_total,
            'cart_subtotal': cart_subtotal,
            'excluded_by': excluded_by
        }
