"""
Сервис для работы с документами поступления (IncomingDocument).

Обеспечивает:
- Создание документов с автонумерацией
- Добавление позиций в черновик
- Проведение документов (создание IncomingBatch и Incoming)
- Отмену документов
"""

from decimal import Decimal
from django.db import transaction
from django.utils import timezone
from django.core.exceptions import ValidationError

from inventory.models import (
    IncomingDocument, IncomingDocumentItem, StockBatch, Stock
)
from inventory.utils.document_generator import generate_incoming_document_number


class IncomingDocumentService:
    """
    Сервис для работы с документами поступления.
    """

    @classmethod
    @transaction.atomic
    def create_document(cls, warehouse, date, receipt_type='supplier', supplier_name=None, notes=None, created_by=None):
        """
        Создать новый документ поступления (черновик).

        Args:
            warehouse: объект Warehouse
            date: дата документа (date)
            receipt_type: тип поступления ('supplier', 'inventory', 'adjustment')
            supplier_name: наименование поставщика (str, опционально, для типа 'supplier')
            notes: примечания (str, опционально)
            created_by: пользователь (User, опционально)

        Returns:
            IncomingDocument
        """
        document = IncomingDocument.objects.create(
            document_number=generate_incoming_document_number(),
            warehouse=warehouse,
            status='draft',
            date=date,
            receipt_type=receipt_type,
            supplier_name=supplier_name if receipt_type == 'supplier' else None,
            notes=notes,
            created_by=created_by
        )
        return document

    @classmethod
    @transaction.atomic
    def add_item(cls, document, product, quantity, cost_price, notes=None):
        """
        Добавить позицию в документ поступления.

        Args:
            document: IncomingDocument
            product: Product
            quantity: Decimal - количество товара
            cost_price: Decimal - закупочная цена
            notes: str - примечания

        Returns:
            IncomingDocumentItem

        Raises:
            ValidationError: если документ не черновик
        """
        if document.status != 'draft':
            raise ValidationError(
                "Нельзя добавлять позиции в проведённый или отменённый документ"
            )

        quantity = Decimal(str(quantity))
        if quantity <= 0:
            raise ValidationError("Количество должно быть больше нуля")

        cost_price = Decimal(str(cost_price))
        if cost_price < 0:
            raise ValidationError("Закупочная цена не может быть отрицательной")

        # Создаем позицию документа
        item = IncomingDocumentItem.objects.create(
            document=document,
            product=product,
            quantity=quantity,
            cost_price=cost_price,
            notes=notes
        )

        return item

    @classmethod
    @transaction.atomic
    def update_item(cls, item, quantity=None, cost_price=None, notes=None):
        """
        Обновить позицию документа.

        Args:
            item: IncomingDocumentItem
            quantity: новое количество (опционально)
            cost_price: новая закупочная цена (опционально)
            notes: новые примечания (опционально)

        Returns:
            IncomingDocumentItem

        Raises:
            ValidationError: если документ не черновик
        """
        if item.document.status != 'draft':
            raise ValidationError(
                "Нельзя редактировать позиции проведённого или отменённого документа"
            )

        if quantity is not None:
            quantity = Decimal(str(quantity))
            if quantity <= 0:
                raise ValidationError("Количество должно быть больше нуля")
            item.quantity = quantity

        if cost_price is not None:
            cost_price = Decimal(str(cost_price))
            if cost_price < 0:
                raise ValidationError("Закупочная цена не может быть отрицательной")
            item.cost_price = cost_price

        if notes is not None:
            item.notes = notes

        item.save()
        return item

    @classmethod
    @transaction.atomic
    def remove_item(cls, item):
        """
        Удалить позицию из документа.

        Args:
            item: IncomingDocumentItem

        Raises:
            ValidationError: если документ не черновик
        """
        if item.document.status != 'draft':
            raise ValidationError(
                "Нельзя удалять позиции из проведённого или отменённого документа"
            )

        # Удаляем позицию
        item.delete()

    @classmethod
    @transaction.atomic
    def confirm_document(cls, document, confirmed_by=None):
        """
        Провести документ поступления.

        Процесс:
        1. Проверяем что документ - черновик и имеет позиции
        2. Для каждой позиции создаем StockBatch напрямую
        3. Обновляем Stock
        4. Меняем статус документа на 'confirmed'

        Args:
            document: IncomingDocument
            confirmed_by: User - кто проводит документ

        Returns:
            dict: результат проведения

        Raises:
            ValidationError: если документ нельзя провести
        """
        if document.status != 'draft':
            raise ValidationError(
                f"Документ уже проведён или отменён (статус: {document.get_status_display()})"
            )

        if not document.items.exists():
            raise ValidationError("Нельзя провести пустой документ")

        # Создаем StockBatch напрямую для каждого товара
        batches_created = []
        total_cost = Decimal('0')

        for item in document.items.select_related('product'):
            # Создаем партию товара на складе
            stock_batch = StockBatch.objects.create(
                product=item.product,
                warehouse=document.warehouse,
                quantity=item.quantity,
                cost_price=item.cost_price,
                is_active=True
            )
            batches_created.append(stock_batch)
            total_cost += item.total_cost

            # Обрабатываем ожидающие продажи "в минус" для этого товара
            cls._process_pending_sales(
                product=item.product,
                warehouse=document.warehouse,
                new_batch=stock_batch
            )

            # Обновляем или создаем запись в Stock
            stock, _ = Stock.objects.get_or_create(
                product=item.product,
                warehouse=document.warehouse
            )
            # Пересчитываем остаток из всех активных партий
            stock.refresh_from_batches()

        # Обновляем статус документа
        document.status = 'confirmed'
        document.confirmed_by = confirmed_by
        document.confirmed_at = timezone.now()
        document.save(update_fields=['status', 'confirmed_by', 'confirmed_at', 'updated_at'])

        return {
            'document': document,
            'batches_created': len(batches_created),
            'total_quantity': document.total_quantity,
            'total_cost': total_cost
        }

    @classmethod
    def _process_pending_sales(cls, product, warehouse, new_batch):
        """
        Привязать ожидающие продажи "в минус" к новой партии по FIFO.
        Себестоимость берётся из этой партии.

        Args:
            product: объект Product
            warehouse: объект Warehouse
            new_batch: объект StockBatch (только что созданная партия)
        """
        from inventory.models import Sale, SaleBatchAllocation

        # Ожидающие продажи по дате (старые первыми - FIFO)
        pending_sales = Sale.objects.filter(
            product=product,
            warehouse=warehouse,
            is_pending_cost=True,
            pending_quantity__gt=0
        ).order_by('date')

        available_qty = new_batch.quantity

        for sale in pending_sales:
            if available_qty <= 0:
                break

            qty_to_allocate = min(sale.pending_quantity, available_qty)

            # Создаем SaleBatchAllocation с себестоимостью из приёмки
            SaleBatchAllocation.objects.create(
                sale=sale,
                batch=new_batch,
                quantity=qty_to_allocate,
                cost_price=new_batch.cost_price
            )

            # Уменьшаем pending в Sale
            sale.pending_quantity -= qty_to_allocate
            if sale.pending_quantity <= 0:
                sale.is_pending_cost = False
            sale.save(update_fields=['pending_quantity', 'is_pending_cost'])

            # Уменьшаем партию (товар уже был "продан" ранее)
            new_batch.quantity -= qty_to_allocate
            available_qty -= qty_to_allocate

        # Сохраняем партию с оставшимся количеством
        if new_batch.quantity <= 0:
            new_batch.is_active = False
        new_batch.save(update_fields=['quantity', 'is_active'])

    @classmethod
    @transaction.atomic
    def cancel_document(cls, document):
        """
        Отменить документ поступления (черновик).

        Args:
            document: IncomingDocument

        Returns:
            IncomingDocument

        Raises:
            ValidationError: если документ уже проведён
        """
        if document.status == 'confirmed':
            raise ValidationError(
                "Нельзя отменить проведённый документ. "
                "Создайте новый документ для корректировки."
            )

        if document.status == 'cancelled':
            raise ValidationError("Документ уже отменён")

        # Обновляем статус документа
        document.status = 'cancelled'
        document.save(update_fields=['status', 'updated_at'])

        return document

    @staticmethod
    def get_draft_documents(warehouse=None):
        """
        Получить все черновики документов поступления.

        Args:
            warehouse: фильтр по складу (опционально)

        Returns:
            QuerySet[IncomingDocument]
        """
        qs = IncomingDocument.objects.filter(status='draft')
        if warehouse:
            qs = qs.filter(warehouse=warehouse)
        return qs.select_related('warehouse', 'created_by').prefetch_related('items')

    @staticmethod
    def get_today_drafts(warehouse):
        """
        Получить черновики за сегодня для склада.

        Args:
            warehouse: Warehouse

        Returns:
            QuerySet[IncomingDocument]
        """
        today = timezone.now().date()

        return IncomingDocument.objects.filter(
            warehouse=warehouse,
            status='draft',
            date=today
        ).select_related('warehouse', 'created_by')

