"""
Процессор для обработки инвентаризации.
Основной функционал:
- Обработка расхождений между фактом и системой
- Автоматическое создание WriteOffDocument для недостач (черновик)
- Автоматическое создание IncomingDocument для излишков (черновик)
"""

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

from inventory.models import (
    Inventory, InventoryLine, StockBatch, Stock
)
from inventory.services.batch_manager import StockBatchManager
from inventory.services.writeoff_document_service import WriteOffDocumentService
from inventory.services.incoming_document_service import IncomingDocumentService


class InventoryProcessor:
    """
    Обработчик инвентаризации с автоматической коррекцией остатков.
    """

    @staticmethod
    @transaction.atomic
    def process_inventory(inventory_id):
        """
        Обработать инвентаризацию:
        - Для недостач (разница < 0): создать WriteOffDocument (черновик) с позициями
        - Для излишков (разница > 0): создать IncomingDocument (черновик) с позициями
        - Обновить статус inventory и lines
        - НЕ проводить документы сразу - они остаются в статусе 'draft'

        Args:
            inventory_id: ID объекта Inventory

        Returns:
            dict: {
                'inventory': Inventory,
                'processed_lines': int,
                'writeoff_document': WriteOffDocument или None,
                'incoming_document': IncomingDocument или None,
                'errors': [...]
            }
        """
        inventory = Inventory.objects.get(id=inventory_id)
        # Получаем все строки инвентаризации для сохранения snapshot
        all_lines = InventoryLine.objects.filter(inventory=inventory)
        # Получаем только необработанные строки для обработки
        lines = all_lines.filter(processed=False)

        errors = []
        writeoff_document = None
        incoming_document = None

        # Сохраняем snapshot-значения для ВСЕХ строк ПЕРЕД обработкой
        # Это нужно чтобы зафиксировать состояние на момент подсчета
        from inventory.models import Stock
        for line in all_lines:
            try:
                stock, _ = Stock.objects.get_or_create(
                    product=line.product,
                    warehouse=inventory.warehouse
                )
                stock.refresh_from_batches()
                
                # Пересчитываем разницу перед сохранением snapshot
                # чтобы убедиться что она актуальна
                current_difference = (line.quantity_fact + stock.quantity_reserved) - stock.quantity_available
                
                # Сохраняем snapshot-значения на момент завершения
                line.snapshot_quantity_available = stock.quantity_available
                line.snapshot_quantity_reserved = stock.quantity_reserved
                line.snapshot_quantity_system = stock.quantity_free
                line.snapshot_difference = current_difference
                line.save(update_fields=[
                    'snapshot_quantity_available',
                    'snapshot_quantity_reserved',
                    'snapshot_quantity_system',
                    'snapshot_difference'
                ])
            except Exception as e:
                errors.append({
                    'line': line,
                    'error': f'Ошибка сохранения snapshot: {str(e)}'
                })

        # Собираем недостачи и излишки
        deficit_lines = []
        surplus_lines = []

        try:
            for line in lines:
                try:
                    if line.difference < 0:
                        # Недостача
                        deficit_lines.append(line)
                    elif line.difference > 0:
                        # Излишек
                        surplus_lines.append(line)

                    # Отмечаем строку как обработанную
                    line.processed = True
                    line.save(update_fields=['processed'])

                except Exception as e:
                    errors.append({
                        'line': line,
                        'error': str(e)
                    })

            # Создаем WriteOffDocument для недостач (если есть)
            if deficit_lines:
                writeoff_document = InventoryProcessor._create_writeoff_document(
                    inventory, deficit_lines
                )

            # Создаем IncomingDocument для излишков (если есть)
            if surplus_lines:
                incoming_document = InventoryProcessor._create_incoming_document(
                    inventory, surplus_lines
                )

            # Обновляем статус инвентаризации
            inventory.status = 'completed'
            inventory.save(update_fields=['status'])

        except Exception as e:
            errors.append({
                'inventory': inventory,
                'error': str(e)
            })

        return {
            'inventory': inventory,
            'processed_lines': lines.count(),
            'writeoff_document': writeoff_document,
            'incoming_document': incoming_document,
            'errors': errors
        }

    @staticmethod
    def _create_writeoff_document(inventory, deficit_lines):
        """
        Создать документ списания (WriteOffDocument) для недостач при инвентаризации.
        Документ создается в статусе 'draft' и не проводится сразу.

        Args:
            inventory: объект Inventory
            deficit_lines: список InventoryLine с negative difference

        Returns:
            WriteOffDocument
        """
        if not deficit_lines:
            return None

        # Создаем документ списания (черновик)
        writeoff_document = WriteOffDocumentService.create_document(
            warehouse=inventory.warehouse,
            date=inventory.date.date(),
            notes=f'Списание по результатам инвентаризации #{inventory.id}',
            created_by=None  # Можно добавить пользователя если передавать в process_inventory
        )

        # Связываем документ с инвентаризацией
        writeoff_document.inventory = inventory
        writeoff_document.save(update_fields=['inventory'])

        # Добавляем позиции в документ
        for line in deficit_lines:
            quantity_to_writeoff = abs(line.difference)
            WriteOffDocumentService.add_item(
                document=writeoff_document,
                product=line.product,
                quantity=quantity_to_writeoff,
                reason='inventory',
                notes=f'Инвентаризация #{inventory.id}, строка #{line.id}'
            )

        return writeoff_document

    @staticmethod
    def _create_incoming_document(inventory, surplus_lines):
        """
        Создать документ поступления (IncomingDocument) для излишков при инвентаризации.
        Документ создается в статусе 'draft' и не проводится сразу.

        Args:
            inventory: объект Inventory
            surplus_lines: список InventoryLine с positive difference

        Returns:
            IncomingDocument
        """
        if not surplus_lines:
            return None

        # Создаем документ поступления (черновик) с типом 'inventory'
        incoming_document = IncomingDocumentService.create_document(
            warehouse=inventory.warehouse,
            date=inventory.date.date(),
            receipt_type='inventory',
            supplier_name=None,
            notes=f'Оприходование по результатам инвентаризации #{inventory.id}',
            created_by=None  # Можно добавить пользователя если передавать в process_inventory
        )

        # Связываем документ с инвентаризацией
        incoming_document.inventory = inventory
        incoming_document.save(update_fields=['inventory'])

        # Добавляем позиции в документ
        for line in surplus_lines:
            quantity_surplus = line.difference

            # Получаем последнюю known cost_price
            cost_price = InventoryProcessor._get_last_cost_price(
                line.product,
                inventory.warehouse
            )

            IncomingDocumentService.add_item(
                document=incoming_document,
                product=line.product,
                quantity=quantity_surplus,
                cost_price=cost_price,
                notes=f'Инвентаризация #{inventory.id}, строка #{line.id}'
            )

        return incoming_document

    @staticmethod
    def _get_last_cost_price(product, warehouse):
        """
        Получить последнюю известную закупочную цену товара на складе.
        Используется для создания новой партии при излишке.

        Порядок поиска:
        1. Последняя активная партия на этом складе
        2. Последняя активная партия на любом складе
        3. cost_price из карточки Product (если есть)
        4. Дефолт 0 (если ничего не найдено)

        Args:
            product: объект Product
            warehouse: объект Warehouse

        Returns:
            Decimal - закупочная цена
        """
        from inventory.models import StockBatch

        # Вариант 1: последняя партия на этом складе
        last_batch = StockBatch.objects.filter(
            product=product,
            warehouse=warehouse,
            is_active=True
        ).order_by('-created_at').first()

        if last_batch:
            return last_batch.cost_price

        # Вариант 2: последняя партия на любом складе
        last_batch_any = StockBatch.objects.filter(
            product=product,
            is_active=True
        ).order_by('-created_at').first()

        if last_batch_any:
            return last_batch_any.cost_price

        # Вариант 3: cost_price из карточки товара
        if product.cost_price:
            return product.cost_price

        # Вариант 4: ноль (не должно быть)
        return Decimal('0')

    @staticmethod
    def create_inventory_lines_from_current_stock(inventory):
        """
        Автоматически создать InventoryLine для всех товаров на складе.
        Используется для удобства: оператор может сразу начать вводить фактические
        количества, имея под рукой системные остатки.

        Args:
            inventory: объект Inventory
        """
        from inventory.models import StockBatch

        # Получаем все товары, которые есть на этом складе
        batches = StockBatch.objects.filter(
            warehouse=inventory.warehouse,
            is_active=True
        ).values('product').distinct()

        for batch_dict in batches:
            product = batch_dict['product']

            # Рассчитываем системный остаток
            quantity_system = StockBatchManager.get_total_stock(product, inventory.warehouse)

            # Создаем строку инвентаризации (факт будет заполнен оператором)
            InventoryLine.objects.get_or_create(
                inventory=inventory,
                product_id=product,
                defaults={
                    'quantity_system': quantity_system,
                    'quantity_fact': 0,  # Оператор должен заполнить
                }
            )

    @staticmethod
    def get_inventory_report(inventory):
        """
        Получить отчет по инвентаризации.

        Args:
            inventory: объект Inventory

        Returns:
            dict: {
                'inventory': Inventory,
                'total_lines': int,
                'total_deficit': Decimal,
                'total_surplus': Decimal,
                'lines': [...]
            }
        """
        lines = InventoryLine.objects.filter(inventory=inventory).select_related('product')

        total_deficit = Decimal('0')
        total_surplus = Decimal('0')
        lines_data = []

        for line in lines:
            if line.difference < 0:
                total_deficit += abs(line.difference)
            elif line.difference > 0:
                total_surplus += line.difference

            lines_data.append({
                'line': line,
                'system_value': line.quantity_system * line.product.cost_price,
                'fact_value': line.quantity_fact * line.product.cost_price,
                'value_difference': (line.quantity_fact - line.quantity_system) * line.product.cost_price,
            })

        return {
            'inventory': inventory,
            'total_lines': lines.count(),
            'total_deficit': total_deficit,
            'total_surplus': total_surplus,
            'lines': lines_data
        }
