"""
Процессор для обработки продаж.
Основной функционал:
- Создание операции Sale
- FIFO-списание товара из партий
- Фиксирование распределения партий для аудита
"""

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

from inventory.models import Sale, SaleBatchAllocation
from inventory.services.batch_manager import StockBatchManager


class SaleProcessor:
    """
    Обработчик продаж с автоматическим FIFO-списанием.
    """

    @staticmethod
    @transaction.atomic
    def create_sale_from_reservation(reservation, order=None):
        """
        Создать продажу на основе резерва.
        Используется для продажи с витрины.

        Args:
            reservation: объект Reservation
            order: (опционально) объект Order

        Returns:
            Объект Sale
        """
        # Определяем цену продажи из заказа или из товара
        if order and reservation.order_item:
            item = reservation.order_item
            # Цена за единицу с учётом всех скидок (позиция + заказ)
            item_subtotal = Decimal(str(item.price)) * Decimal(str(item.quantity))

            # Скидка на позицию
            item_discount = Decimal(str(item.discount_amount)) if item.discount_amount else Decimal('0')

            # Скидка на заказ (распределяется пропорционально доле позиции в заказе)
            # Вычисляем как разницу между subtotal и total_amount (так как discount_amount может быть 0)
            order_total = order.subtotal if hasattr(order, 'subtotal') else Decimal('0')
            # Скидка = subtotal - (total_amount - delivery) (вычитаем доставку, если есть)
            delivery_cost = Decimal(str(order.delivery.cost)) if hasattr(order, 'delivery') and order.delivery else Decimal('0')
            order_discount = (order_total - (Decimal(str(order.total_amount)) - delivery_cost)) if order_total > 0 else Decimal('0')

            total_discount = item_discount + order_discount
            if total_discount and item.quantity > 0:
                # Распределяем общую скидку пропорционально доле позиции
                item_order_discount = order_discount * (item_subtotal / order_total) if order_total > 0 else Decimal('0')
                total_discount = item_discount + item_order_discount
                price_with_discount = (item_subtotal - total_discount) / Decimal(str(item.quantity))
            else:
                price_with_discount = Decimal(str(item.price))

            # Пересчитываем цену в базовые единицы
            if item.sales_unit and item.conversion_factor_snapshot:
                sale_price = price_with_discount * item.conversion_factor_snapshot
            else:
                sale_price = price_with_discount
        else:
            # Цена из товара
            sale_price = reservation.product.actual_price or Decimal('0')

        # Создаём продажу с FIFO-списанием
        sale = SaleProcessor.create_sale(
            product=reservation.product,
            warehouse=reservation.warehouse,
            quantity=reservation.quantity,
            sale_price=sale_price,
            order=order,
            document_number=None
        )

        return sale

    @staticmethod
    @transaction.atomic
    def create_sale(product, warehouse, quantity, sale_price, order=None, document_number=None, sales_unit=None):
        """
        Создать операцию продажи и произвести FIFO-списание.

        Процесс:
        1. Создаем запись Sale
        2. Списываем товар по FIFO из партий
        3. Фиксируем распределение в SaleBatchAllocation для аудита

        Args:
            product: объект Product
            warehouse: объект Warehouse
            quantity: Decimal - количество товара В БАЗОВЫХ ЕДИНИЦАХ.
                     Для списания со склада всегда используются базовые единицы.
            sale_price: Decimal - цена продажи
            order: (опционально) объект Order
            document_number: (опционально) номер документа
            sales_unit: (опционально) объект ProductSalesUnit - единица продажи.
                       Используется ТОЛЬКО для сохранения снимка (не для конверсии).

        Returns:
            Объект Sale

        Raises:
            ValueError: если недостаточно товара или некорректные данные
        """
        if quantity <= 0:
            raise ValueError("Количество должно быть больше нуля")

        if sale_price < 0:
            raise ValueError("Цена продажи не может быть отрицательной")

        # quantity УЖЕ в базовых единицах, конверсия не нужна
        quantity_base = quantity
        unit_name_snapshot = sales_unit.name if sales_unit else ''

        # Создаем запись Sale
        # ВАЖНО: Устанавливаем processed=True сразу, чтобы сигнал process_sale_fifo не сработал
        # (списание делаем вручную ниже, чтобы избежать двойного списания)
        sale = Sale.objects.create(
            product=product,
            warehouse=warehouse,
            quantity=quantity_base,  # В базовых единицах (для истории/отчётов)
            quantity_base=quantity_base,  # В базовых единицах (для списания)
            sale_price=sale_price,
            order=order,
            document_number=document_number,
            processed=True,  # Сразу отмечаем как обработанную
            sales_unit=sales_unit,
            unit_name_snapshot=unit_name_snapshot,
            is_pending_cost=False,
            pending_quantity=Decimal('0')
        )

        # Списываем товар по FIFO в БАЗОВЫХ единицах
        # exclude_order позволяет не считать резервы этого заказа как занятые
        # (резервы переводятся в converted_to_sale ПОСЛЕ создания Sale)
        # allow_negative=True разрешает продажи "в минус"
        allocations, pending = StockBatchManager.write_off_by_fifo(
            product, warehouse, quantity_base, exclude_order=order, allow_negative=True
        )

        # Фиксируем распределение для аудита
        for batch, qty_allocated in allocations:
            SaleBatchAllocation.objects.create(
                sale=sale,
                batch=batch,
                quantity=qty_allocated,
                cost_price=batch.cost_price
            )

        # Если есть pending - это продажа "в минус"
        if pending > 0:
            sale.is_pending_cost = True
            sale.pending_quantity = pending
            sale.save(update_fields=['is_pending_cost', 'pending_quantity'])

        # Обновляем Stock (теперь учитывает pending_sales)
        StockBatchManager.refresh_stock_cache(product, warehouse)

        return sale

    @staticmethod
    def get_sale_cost_analysis(sale):
        """
        Получить анализ себестоимости продажи.
        Возвращает список партий, использованных при продаже, с расчетом прибыли.

        Args:
            sale: объект Sale

        Returns:
            dict: {
                'total_quantity': Decimal,
                'total_cost': Decimal,  # сумма себестоимости
                'total_revenue': Decimal,  # сумма выручки
                'profit': Decimal,
                'profit_margin': Decimal,  # процент прибыли
                'allocations': [  # распределение по партиям
                    {
                        'batch': StockBatch,
                        'quantity': Decimal,
                        'cost_price': Decimal,
                        'batch_cost': Decimal,
                        'revenue': Decimal,
                        'batch_profit': Decimal
                    },
                    ...
                ]
            }
        """
        allocations = SaleBatchAllocation.objects.filter(sale=sale).select_related('batch')

        allocation_details = []
        total_cost = Decimal('0')
        total_revenue = sale.quantity * sale.sale_price

        for alloc in allocations:
            batch_cost = alloc.quantity * alloc.cost_price
            batch_revenue = alloc.quantity * sale.sale_price
            batch_profit = batch_revenue - batch_cost

            total_cost += batch_cost

            allocation_details.append({
                'batch': alloc.batch,
                'quantity': alloc.quantity,
                'cost_price': alloc.cost_price,
                'batch_cost': batch_cost,
                'revenue': batch_revenue,
                'batch_profit': batch_profit
            })

        total_profit = total_revenue - total_cost
        profit_margin = (total_profit / total_revenue * 100) if total_revenue > 0 else Decimal('0')

        return {
            'total_quantity': sale.quantity,
            'total_cost': total_cost,
            'total_revenue': total_revenue,
            'profit': total_profit,
            'profit_margin': round(profit_margin, 2),
            'allocations': allocation_details
        }

    @staticmethod
    def get_sales_report(warehouse, product=None, date_from=None, date_to=None):
        """
        Получить отчет по продажам с расчетом прибыли.

        Args:
            warehouse: объект Warehouse
            product: (опционально) объект Product для фильтрации
            date_from: (опционально) начальная дата
            date_to: (опционально) конечная дата

        Returns:
            dict: {
                'total_sales': int,  # количество операций
                'total_quantity': Decimal,
                'total_revenue': Decimal,
                'total_cost': Decimal,
                'total_profit': Decimal,
                'avg_profit_margin': Decimal,
                'sales': [...]  # подробная информация по каждой продаже
            }
        """
        from inventory.models import Sale

        qs = Sale.objects.filter(warehouse=warehouse, processed=True)

        if product:
            qs = qs.filter(product=product)

        if date_from:
            qs = qs.filter(date__gte=date_from)

        if date_to:
            qs = qs.filter(date__lte=date_to)

        sales_list = []
        total_revenue = Decimal('0')
        total_cost = Decimal('0')
        total_quantity = Decimal('0')

        for sale in qs.select_related('product', 'order'):
            analysis = SaleProcessor.get_sale_cost_analysis(sale)

            total_revenue += analysis['total_revenue']
            total_cost += analysis['total_cost']
            total_quantity += analysis['total_quantity']

            sales_list.append({
                'sale': sale,
                'analysis': analysis
            })

        total_profit = total_revenue - total_cost
        avg_profit_margin = (
            (total_profit / total_revenue * 100) if total_revenue > 0 else Decimal('0')
        )

        return {
            'total_sales': len(sales_list),
            'total_quantity': total_quantity,
            'total_revenue': total_revenue,
            'total_cost': total_cost,
            'total_profit': total_profit,
            'avg_profit_margin': round(avg_profit_margin, 2),
            'sales': sales_list
        }
