# -*- coding: utf-8 -*-
from django.shortcuts import render, redirect, get_object_or_404
from django.views.generic import ListView, CreateView, DetailView, View, FormView
from django.urls import reverse_lazy
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib import messages
from django.http import HttpResponseRedirect, JsonResponse
from django.db import transaction
from django.views.decorators.http import require_http_methods
from django.utils.decorators import method_decorator
from decimal import Decimal
import json

from ..models import Inventory, InventoryLine, Stock
from ..forms import InventoryForm, InventoryLineForm
from ..services.inventory_processor import InventoryProcessor
from ..services.writeoff_document_service import WriteOffDocumentService
from ..services.incoming_document_service import IncomingDocumentService
from django.core.exceptions import ValidationError
from products.models import Product, ProductCategory, ProductTag


class InventoryListView(LoginRequiredMixin, ListView):
    """
    Список всех инвентаризаций по складам
    """
    model = Inventory
    template_name = 'inventory/inventory/inventory_list.html'
    context_object_name = 'inventories'
    paginate_by = 20

    def get_queryset(self):
        queryset = Inventory.objects.select_related('warehouse').order_by('-date')

        # Фильтры (если переданы)
        warehouse_id = self.request.GET.get('warehouse')
        status = self.request.GET.get('status')

        if warehouse_id:
            queryset = queryset.filter(warehouse_id=warehouse_id)
        if status:
            queryset = queryset.filter(status=status)

        return queryset


class InventoryCreateView(LoginRequiredMixin, CreateView):
    """
    Начало новой инвентаризации по конкретному складу.
    Переводит инвентаризацию в статус 'processing'.
    """
    model = Inventory
    form_class = InventoryForm
    template_name = 'inventory/inventory/inventory_form.html'

    def form_valid(self, form):
        from inventory.utils.document_generator import generate_inventory_document_number
        
        form.instance.status = 'processing'
        form.instance.document_number = generate_inventory_document_number()
        
        # Автоматически проставляем роль пользователя, который создает инвентаризацию
        # Если у пользователя есть роль в тенанте - проставляем её, иначе оставляем NULL
        try:
            if hasattr(self.request.user, 'tenant_role'):
                user_role = getattr(self.request.user, 'tenant_role', None)
                if user_role and user_role.is_active:
                    form.instance.conducted_by = user_role
        except (AttributeError, Exception):
            # Если у пользователя нет роли (суперюзер/админ без роли) - оставляем NULL
            pass
        
        messages.success(
            self.request,
            f'Инвентаризация склада "{form.instance.warehouse.name}" начата.'
        )
        return super().form_valid(form)
    
    def get_success_url(self):
        """Перенаправляем на страницу редактирования созданной инвентаризации"""
        return reverse_lazy('inventory:inventory-detail', kwargs={'pk': self.object.pk})


class InventoryDetailView(LoginRequiredMixin, DetailView):
    """
    Детальный просмотр инвентаризации с её строками.
    Позволяет добавлять строки и заполнять фактические количества.
    """
    model = Inventory
    template_name = 'inventory/inventory/inventory_detail.html'
    context_object_name = 'inventory'
    
    def get_queryset(self):
        """Оптимизация: предзагружаем warehouse"""
        return Inventory.objects.select_related('warehouse')

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        from inventory.models import Stock, StockBatch, Reservation
        from django.db.models import Sum, Q
        from decimal import Decimal
        
        # Получаем все строки этой инвентаризации с оптимизацией
        lines = InventoryLine.objects.filter(
            inventory=self.object
        ).select_related('product').order_by('product__name')
        
        if not lines.exists():
            context['lines'] = []
            context['categories'] = ProductCategory.objects.filter(is_active=True).order_by('name')
            context['tags'] = ProductTag.objects.filter(is_active=True).order_by('name')
            if self.object.status == 'completed':
                context['writeoff_document'] = self.object.writeoff_documents.filter(status='draft').first()
                context['incoming_document'] = self.object.incoming_documents.filter(status='draft').first()
            return context
        
        # Получаем все product_id из строк
        product_ids = [line.product_id for line in lines]
        warehouse = self.object.warehouse
        
        # Получаем или создаем все Stock объекты одним запросом
        stocks = Stock.objects.filter(
            product_id__in=product_ids,
            warehouse=warehouse
        )
        existing_stocks = {stock.product_id: stock for stock in stocks}
        
        # Создаем недостающие Stock объекты
        missing_product_ids = set(product_ids) - set(existing_stocks.keys())
        if missing_product_ids:
            new_stocks = [
                Stock(product_id=pid, warehouse=warehouse)
                for pid in missing_product_ids
            ]
            Stock.objects.bulk_create(new_stocks)
            # Перезагружаем все Stock объекты
            stocks = Stock.objects.filter(
                product_id__in=product_ids,
                warehouse=warehouse
            )
            existing_stocks = {stock.product_id: stock for stock in stocks}
        
        # Bulk запрос для получения всех StockBatch данных
        stock_batches_qs = StockBatch.objects.filter(
            product_id__in=product_ids,
            warehouse=warehouse,
            is_active=True
        ).values('product_id').annotate(
            total_qty=Sum('quantity')
        )
        stock_batches_dict = {
            item['product_id']: item['total_qty'] or Decimal('0')
            for item in stock_batches_qs
        }
        
        # Bulk запрос для получения всех Reservation данных
        reservations_qs = Reservation.objects.filter(
            product_id__in=product_ids,
            warehouse=warehouse,
            status='reserved'
        ).values('product_id').annotate(
            total_reserved=Sum('quantity')
        )
        reservations_dict = {
            item['product_id']: item['total_reserved'] or Decimal('0')
            for item in reservations_qs
        }
        
        # Обновляем все Stock объекты bulk update
        stocks_to_update = []
        for stock in stocks:
            product_id = stock.product_id
            stock.quantity_available = stock_batches_dict.get(product_id, Decimal('0'))
            stock.quantity_reserved = reservations_dict.get(product_id, Decimal('0'))
            stocks_to_update.append(stock)
        
        if stocks_to_update:
            Stock.objects.bulk_update(
                stocks_to_update,
                ['quantity_available', 'quantity_reserved', 'updated_at'],
                batch_size=100
            )
        
        # Добавляем quantity_reserved и обновляем quantity_system для каждой строки
        lines_with_reserved = []
        for line in lines:
            # Для завершенных инвентаризаций используем snapshot-значения
            if self.object.status == 'completed':
                # Используем snapshot-значения если они есть (для новых записей)
                if line.snapshot_quantity_available is not None:
                    line.quantity_available = line.snapshot_quantity_available
                    line.quantity_reserved = line.snapshot_quantity_reserved
                    line.quantity_system = line.snapshot_quantity_system
                    # Используем snapshot_difference, если он есть, иначе пересчитываем
                    if line.snapshot_difference is not None:
                        line.difference = line.snapshot_difference
                    else:
                        # Fallback: пересчитываем из snapshot-значений
                        line.difference = (line.quantity_fact + line.snapshot_quantity_reserved) - line.snapshot_quantity_available
                else:
                    # Fallback для старых записей без snapshot
                    # Используем текущие значения Stock, но это не идеально
                    # так как остатки уже могли измениться после завершения инвентаризации
                    stock = existing_stocks.get(line.product_id)
                    if not stock:
                        stock, _ = Stock.objects.get_or_create(
                            product=line.product,
                            warehouse=warehouse
                        )
                        stock.refresh_from_batches()
                    line.quantity_reserved = stock.quantity_reserved
                    line.quantity_available = stock.quantity_available
                    # Для старых записей используем сохраненное quantity_system из модели
                    # и пересчитываем разницу
                    line.difference = (line.quantity_fact + line.quantity_reserved) - line.quantity_available
            else:
                # Для незавершенных инвентаризаций используем актуальные значения
                stock = existing_stocks.get(line.product_id)
                if not stock:
                    stock, _ = Stock.objects.get_or_create(
                        product=line.product,
                        warehouse=warehouse
                    )
                    stock.refresh_from_batches()
                
                # Используем актуальное свободное количество из Stock
                line.quantity_system = stock.quantity_free
                line.quantity_reserved = stock.quantity_reserved
                line.quantity_available = stock.quantity_available
                
                # Пересчитываем разницу по новой формуле: (quantity_fact + quantity_reserved) - quantity_available
                line.difference = (line.quantity_fact + line.quantity_reserved) - line.quantity_available
            
            lines_with_reserved.append(line)
        
        context['lines'] = lines_with_reserved
        
        # Получаем категории и теги для компонента поиска товаров
        context['categories'] = ProductCategory.objects.filter(is_active=True).order_by('name')
        context['tags'] = ProductTag.objects.filter(is_active=True).order_by('name')
        
        # Получаем созданные документы (если инвентаризация завершена)
        if self.object.status == 'completed':
            context['writeoff_document'] = self.object.writeoff_documents.first()
            context['incoming_document'] = self.object.incoming_documents.first()
        
        return context


class InventoryLineCreateBulkView(LoginRequiredMixin, View):
    """
    Форма для массового внесения результатов инвентаризации.
    Позволяет заполнить результаты пересчета для всех товаров на складе.
    """
    template_name = 'inventory/inventory/inventory_line_bulk_form.html'

    def get_context_data(self, inventory_id, **kwargs):
        inventory = Inventory.objects.get(pk=inventory_id)
        return {
            'inventory': inventory,
            'products': inventory.warehouse.stock_batches.values_list(
                'product', flat=True
            ).distinct()
        }

    def get(self, request, pk):
        inventory = Inventory.objects.get(pk=pk)
        context = {
            'inventory': inventory,
            'lines': InventoryLine.objects.filter(inventory=inventory).select_related('product')
        }
        return render(request, self.template_name, context)

    def post(self, request, pk):
        inventory = Inventory.objects.get(pk=pk)
        # Здесь будет логика обработки массового ввода данных
        # TODO: Реализовать обработку формы с множественными строками
        messages.success(request, 'Результаты инвентаризации добавлены.')
        return redirect('inventory:inventory-detail', pk=pk)


class InventoryLineAddView(LoginRequiredMixin, View):
    """
    AJAX view для добавления строки инвентаризации.
    Принимает product_id через POST, создает InventoryLine с quantity_system = quantity_free.
    """
    
    @method_decorator(require_http_methods(["POST"]))
    @transaction.atomic
    def dispatch(self, *args, **kwargs):
        return super().dispatch(*args, **kwargs)
    
    def post(self, request, inventory_id):
        try:
            inventory = get_object_or_404(Inventory, id=inventory_id)
            
            # Проверяем что инвентаризация не завершена
            if inventory.status == 'completed':
                return JsonResponse({
                    'success': False,
                    'error': 'Нельзя добавлять строки в завершенную инвентаризацию'
                }, status=400)
            
            # Получаем product_id из POST
            product_id = request.POST.get('product_id')
            if not product_id:
                return JsonResponse({
                    'success': False,
                    'error': 'Не указан product_id'
                }, status=400)
            
            product = get_object_or_404(Product, id=product_id)
            
            # Проверяем, нет ли уже такой строки
            existing_line = InventoryLine.objects.filter(
                inventory=inventory,
                product=product
            ).first()
            
            if existing_line:
                return JsonResponse({
                    'success': False,
                    'error': f'Товар "{product.name}" уже добавлен в инвентаризацию'
                }, status=400)
            
            # Получаем свободное количество (quantity_free)
            stock, _ = Stock.objects.get_or_create(
                product=product,
                warehouse=inventory.warehouse
            )
            stock.refresh_from_batches()  # Обновить из партий
            quantity_system = stock.quantity_free  # Свободное незарезервированное количество
            quantity_reserved = stock.quantity_reserved
            quantity_available = stock.quantity_available
            
            # Создаем строку инвентаризации
            # Передаем quantity_reserved и quantity_available для корректного расчета разницы в save()
            line = InventoryLine(
                inventory=inventory,
                product=product,
                quantity_system=quantity_system,
                quantity_fact=Decimal('0'),  # Оператор заполнит позже
                processed=False
            )
            line.save(quantity_reserved=quantity_reserved, quantity_available=quantity_available)
            
            return JsonResponse({
                'success': True,
                'line': {
                    'id': line.id,
                    'product_id': product.id,
                    'product_name': product.name,
                    'quantity_system': str(line.quantity_system),
                    'quantity_reserved': str(quantity_reserved),
                    'quantity_available': str(quantity_available),
                    'quantity_fact': str(line.quantity_fact),
                    'difference': str(line.difference),
                    'processed': line.processed
                }
            })
            
        except Exception as e:
            return JsonResponse({
                'success': False,
                'error': str(e)
            }, status=500)


class InventoryLineUpdateView(LoginRequiredMixin, View):
    """
    AJAX view для обновления quantity_fact строки инвентаризации.
    """
    
    @method_decorator(require_http_methods(["POST"]))
    @transaction.atomic
    def dispatch(self, *args, **kwargs):
        return super().dispatch(*args, **kwargs)
    
    def post(self, request, inventory_id, line_id):
        try:
            inventory = get_object_or_404(Inventory, id=inventory_id)
            
            # Проверяем что инвентаризация не завершена
            if inventory.status == 'completed':
                return JsonResponse({
                    'success': False,
                    'error': 'Нельзя редактировать строки завершенной инвентаризации'
                }, status=400)
            
            line = get_object_or_404(InventoryLine, id=line_id, inventory=inventory)
            
            # Проверяем что строка не обработана
            if line.processed:
                return JsonResponse({
                    'success': False,
                    'error': 'Нельзя редактировать обработанную строку'
                }, status=400)
            
            # Получаем quantity_fact из POST
            quantity_fact_str = request.POST.get('quantity_fact')
            if quantity_fact_str is None:
                return JsonResponse({
                    'success': False,
                    'error': 'Не указано quantity_fact'
                }, status=400)
            
            try:
                quantity_fact = Decimal(str(quantity_fact_str))
                if quantity_fact < 0:
                    return JsonResponse({
                        'success': False,
                        'error': 'Количество не может быть отрицательным'
                    }, status=400)
            except (ValueError, TypeError):
                return JsonResponse({
                    'success': False,
                    'error': 'Некорректное значение quantity_fact'
                }, status=400)
            
            # Обновляем quantity_fact
            line.quantity_fact = quantity_fact
            
            # Получаем актуальные данные из Stock для расчета разницы
            stock, _ = Stock.objects.get_or_create(
                product=line.product,
                warehouse=inventory.warehouse
            )
            stock.refresh_from_batches()
            
            # Для незавершенных инвентаризаций обновляем quantity_system динамически
            if inventory.status != 'completed':
                line.quantity_system = stock.quantity_free
            
            # Пересчитываем разницу по новой формуле: (quantity_fact + quantity_reserved) - quantity_available
            line.difference = (line.quantity_fact + stock.quantity_reserved) - stock.quantity_available
            
            # Сохраняем с передачей quantity_reserved и quantity_available для корректного расчета в save()
            line.save(quantity_reserved=stock.quantity_reserved, quantity_available=stock.quantity_available)
            
            return JsonResponse({
                'success': True,
                'line': {
                    'id': line.id,
                    'product_id': line.product.id,
                    'product_name': line.product.name,
                    'quantity_system': str(line.quantity_system),
                    'quantity_reserved': str(stock.quantity_reserved),
                    'quantity_available': str(stock.quantity_available),
                    'quantity_fact': str(line.quantity_fact),
                    'difference': str(line.difference),
                    'processed': line.processed
                }
            })
            
        except Exception as e:
            return JsonResponse({
                'success': False,
                'error': str(e)
            }, status=500)


class InventoryLineDeleteView(LoginRequiredMixin, View):
    """
    AJAX view для удаления строки инвентаризации.
    """
    
    @method_decorator(require_http_methods(["POST"]))
    @transaction.atomic
    def dispatch(self, *args, **kwargs):
        return super().dispatch(*args, **kwargs)
    
    def post(self, request, inventory_id, line_id):
        try:
            inventory = get_object_or_404(Inventory, id=inventory_id)
            
            # Проверяем что инвентаризация не завершена
            if inventory.status == 'completed':
                return JsonResponse({
                    'success': False,
                    'error': 'Нельзя удалять строки завершенной инвентаризации'
                }, status=400)
            
            line = get_object_or_404(InventoryLine, id=line_id, inventory=inventory)
            
            # Проверяем что строка не обработана
            if line.processed:
                return JsonResponse({
                    'success': False,
                    'error': 'Нельзя удалять обработанную строку'
                }, status=400)
            
            product_name = line.product.name
            line.delete()
            
            return JsonResponse({
                'success': True,
                'message': f'Строка для товара "{product_name}" удалена'
            })
            
        except Exception as e:
            return JsonResponse({
                'success': False,
                'error': str(e)
            }, status=500)


class InventoryCompleteView(LoginRequiredMixin, View):
    """
    View для завершения инвентаризации.
    Создает документы списания и оприходования (черновики).
    """
    
    @method_decorator(require_http_methods(["POST"]))
    @transaction.atomic
    def dispatch(self, *args, **kwargs):
        return super().dispatch(*args, **kwargs)
    
    def post(self, request, inventory_id):
        try:
            inventory = get_object_or_404(Inventory, id=inventory_id)
            
            # Проверяем что инвентаризация не завершена
            if inventory.status == 'completed':
                messages.warning(request, 'Инвентаризация уже завершена.')
                return redirect('inventory:inventory-detail', pk=inventory_id)
            
            # Проверяем что есть строки
            lines_count = InventoryLine.objects.filter(inventory=inventory).count()
            if lines_count == 0:
                messages.error(request, 'Нельзя завершить инвентаризацию без строк.')
                return redirect('inventory:inventory-detail', pk=inventory_id)
            
            # Обрабатываем инвентаризацию (создает документы-черновики)
            result = InventoryProcessor.process_inventory(inventory_id)
            
            # Автоматически проводим созданные документы
            writeoff_confirmed = False
            incoming_confirmed = False
            confirmation_errors = []
            
            if result['writeoff_document'] and result['writeoff_document'].status == 'draft':
                try:
                    WriteOffDocumentService.confirm_document(
                        result['writeoff_document'],
                        confirmed_by=request.user
                    )
                    writeoff_confirmed = True
                except ValidationError as e:
                    confirmation_errors.append(f'Ошибка проведения документа списания: {str(e)}')
            
            if result['incoming_document'] and result['incoming_document'].status == 'draft':
                try:
                    IncomingDocumentService.confirm_document(
                        result['incoming_document'],
                        confirmed_by=request.user
                    )
                    incoming_confirmed = True
                except ValidationError as e:
                    confirmation_errors.append(f'Ошибка проведения документа оприходования: {str(e)}')
            
            # Формируем сообщение
            msg_parts = [f'Инвентаризация завершена. Обработано строк: {result["processed_lines"]}.']
            
            if result['writeoff_document']:
                if writeoff_confirmed:
                    msg_parts.append(
                        f'Документ списания {result["writeoff_document"].document_number} создан и автоматически проведен.'
                    )
                else:
                    msg_parts.append(
                        f'Документ списания: {result["writeoff_document"].document_number}.'
                    )
            
            if result['incoming_document']:
                if incoming_confirmed:
                    msg_parts.append(
                        f'Документ оприходования {result["incoming_document"].document_number} создан и автоматически проведен.'
                    )
                else:
                    msg_parts.append(
                        f'Документ оприходования: {result["incoming_document"].document_number}.'
                    )
            
            if result['errors']:
                msg_parts.append(f'Ошибок при обработке: {len(result["errors"])}.')
            
            if confirmation_errors:
                for error in confirmation_errors:
                    messages.error(request, error)
            
            messages.success(request, ' '.join(msg_parts))
            
            return redirect('inventory:inventory-detail', pk=inventory_id)
            
        except Exception as e:
            messages.error(request, f'Ошибка при завершении инвентаризации: {str(e)}')
            return redirect('inventory:inventory-detail', pk=inventory_id)


class InventoryDeleteView(LoginRequiredMixin, View):
    """
    Удаление инвентаризации.
    Можно удалять только инвентаризации со статусом 'draft' или 'processing'.
    Завершенные инвентаризации удалять нельзя, так как документы уже проведены.
    """
    
    @method_decorator(require_http_methods(["POST"]))
    @transaction.atomic
    def dispatch(self, *args, **kwargs):
        return super().dispatch(*args, **kwargs)
    
    def post(self, request, pk):
        try:
            inventory = get_object_or_404(Inventory, pk=pk)
            
            # Проверка статуса - завершенные нельзя удалять
            if inventory.status == 'completed':
                messages.error(
                    request,
                    'Нельзя удалить завершенную инвентаризацию. Документы уже проведены.'
                )
                return redirect('inventory:inventory-detail', pk=pk)
            
            # Проверка связанных документов
            writeoff_docs = inventory.writeoff_documents.all()
            incoming_docs = inventory.incoming_documents.all()
            
            # Проверяем, есть ли проведенные документы
            if writeoff_docs.filter(status='confirmed').exists():
                messages.error(
                    request,
                    'Нельзя удалить инвентаризацию с проведенными документами списания.'
                )
                return redirect('inventory:inventory-detail', pk=pk)
            
            if incoming_docs.filter(status='confirmed').exists():
                messages.error(
                    request,
                    'Нельзя удалить инвентаризацию с проведенными документами поступления.'
                )
                return redirect('inventory:inventory-detail', pk=pk)
            
            # Сохраняем информацию для сообщения
            warehouse_name = inventory.warehouse.name
            document_number = inventory.document_number or f"#{inventory.id}"
            
            # Удаляем документы-черновики вместе с инвентаризацией
            writeoff_docs.filter(status='draft').delete()
            incoming_docs.filter(status='draft').delete()
            
            # Удаляем инвентаризацию (InventoryLine удалятся автоматически через CASCADE)
            inventory.delete()
            
            messages.success(
                request,
                f'Инвентаризация {document_number} склада "{warehouse_name}" успешно удалена.'
            )
            return redirect('inventory:inventory-list')
            
        except Exception as e:
            messages.error(request, f'Ошибка при удалении инвентаризации: {str(e)}')
            return redirect('inventory:inventory-detail', pk=pk)


