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

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

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

from inventory.models import (
    WriteOffDocument, WriteOffDocumentItem, WriteOff,
    Reservation, Stock, StockBatch
)
from inventory.utils.document_generator import generate_writeoff_document_number
from inventory.services.batch_manager import StockBatchManager


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

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

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

        Returns:
            WriteOffDocument
        """
        document = WriteOffDocument.objects.create(
            document_number=generate_writeoff_document_number(),
            warehouse=warehouse,
            status='draft',
            date=date,
            notes=notes,
            created_by=created_by
        )
        return document

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

        Args:
            document: WriteOffDocument
            product: Product
            quantity: Decimal - количество для списания
            reason: str - причина (damage, spoilage, shortage, inventory, other)
            notes: str - примечания

        Returns:
            WriteOffDocumentItem

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

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

        # Проверяем доступное количество
        stock = Stock.objects.filter(
            product=product,
            warehouse=document.warehouse
        ).first()

        if not stock:
            raise ValidationError(
                f"Товар '{product.name}' отсутствует на складе '{document.warehouse.name}'"
            )

        # quantity_free = quantity_available - quantity_reserved
        available = stock.quantity_available - stock.quantity_reserved
        if quantity > available:
            raise ValidationError(
                f"Недостаточно свободного товара '{product.name}'. "
                f"Доступно: {available}, запрашивается: {quantity}"
            )

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

        # Создаем резерв
        reservation = Reservation.objects.create(
            product=product,
            warehouse=document.warehouse,
            quantity=quantity,
            status='reserved',
            writeoff_document_item=item
        )

        # Связываем резерв с позицией
        item.reservation = reservation
        item.save(update_fields=['reservation'])

        return item

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

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

        Returns:
            WriteOffDocumentItem

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

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

            if quantity != item.quantity:
                # Проверяем доступное количество
                stock = Stock.objects.filter(
                    product=item.product,
                    warehouse=item.document.warehouse
                ).first()

                if stock:
                    # Учитываем текущий резерв этой позиции
                    current_reserved = item.reservation.quantity if item.reservation else Decimal('0')
                    available = (stock.quantity_available - stock.quantity_reserved) + current_reserved

                    if quantity > available:
                        raise ValidationError(
                            f"Недостаточно свободного товара. "
                            f"Доступно: {available}, запрашивается: {quantity}"
                        )

                # Обновляем резерв
                if item.reservation:
                    item.reservation.quantity = quantity
                    item.reservation.quantity_base = quantity
                    item.reservation.save(update_fields=['quantity', 'quantity_base'])

                item.quantity = quantity

        if reason is not None:
            item.reason = reason

        if notes is not None:
            item.notes = notes

        item.save()
        return item

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

        Args:
            item: WriteOffDocumentItem

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

        # Освобождаем резерв
        if item.reservation:
            item.reservation.status = 'released'
            item.reservation.released_at = timezone.now()
            item.reservation.save(update_fields=['status', 'released_at'])

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

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

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

        Args:
            document: WriteOffDocument
            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("Нельзя провести пустой документ")

        writeoffs_created = []
        total_cost = Decimal('0')

        for item in document.items.select_related('product', 'reservation'):
            # Получаем партии по FIFO для списания
            batches = StockBatchManager.get_batches_for_fifo(
                item.product,
                document.warehouse
            )

            remaining = item.quantity

            for batch in batches:
                if remaining <= 0:
                    break

                # Сколько можем списать из этой партии
                qty_to_writeoff = min(batch.quantity, remaining)

                # Создаем запись WriteOff (она сама уменьшит batch.quantity в save())
                writeoff = WriteOff.objects.create(
                    batch=batch,
                    quantity=qty_to_writeoff,
                    reason=item.reason,
                    document_number=document.document_number,
                    notes=f"[Документ {document.document_number}] {item.notes or ''}"
                )
                writeoffs_created.append(writeoff)
                total_cost += qty_to_writeoff * batch.cost_price

                remaining -= qty_to_writeoff

            if remaining > 0:
                raise ValidationError(
                    f"Недостаточно товара '{item.product.name}' для списания. "
                    f"Не хватает: {remaining}"
                )

            # Обновляем резерв - помечаем как преобразованный в списание
            if item.reservation:
                item.reservation.status = 'converted_to_writeoff'
                item.reservation.converted_at = timezone.now()
                item.reservation.save(update_fields=['status', 'converted_at'])

        # Обновляем статус документа
        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,
            'writeoffs_created': len(writeoffs_created),
            'total_quantity': document.total_quantity,
            'total_cost': total_cost
        }

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

        Args:
            document: WriteOffDocument

        Returns:
            WriteOffDocument

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

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

        # Освобождаем все резервы
        for item in document.items.select_related('reservation'):
            if item.reservation and item.reservation.status == 'reserved':
                item.reservation.status = 'released'
                item.reservation.released_at = timezone.now()
                item.reservation.save(update_fields=['status', 'released_at'])

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

        return document

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

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

        Returns:
            QuerySet[WriteOffDocument]
        """
        qs = WriteOffDocument.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[WriteOffDocument]
        """
        today = timezone.now().date()

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