from rest_framework import viewsets, status
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from account.permissions import (
    HasTabPermission, HasActionPermission,
    user_can_see_all_shop_data,
)
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import SearchFilter, OrderingFilter
from rest_framework.exceptions import ValidationError
from rest_framework.decorators import action
from django.core.exceptions import ValidationError as DjangoValidationError
from django.db import transaction
from django.db.models import Q, Sum
from shop_management.models.data.purchase_return import PurchaseReturn, PurchaseReturnItem
from shop_management.serializers.data.purchase_return import PurchaseReturnSerializer
from shop_management.models.data.stock import Stock
from shop_management.views.data.pagination import CustomPagination
from shop_management.services.purchase_returns import post_purchase_return, void_purchase_return


class PurchaseReturnViewSet(viewsets.ModelViewSet):
    queryset = PurchaseReturn.objects.all()
    serializer_class = PurchaseReturnSerializer
    permission_classes = [IsAuthenticated, HasTabPermission, HasActionPermission]
    action_permission_map = {"create": "purchases.return_purchase", "post": "purchases.return_purchase", "void": "purchases.void_purchase"}
    required_tab = "purchase_return"
    filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
    filterset_fields = ['shop', 'purchase']
    search_fields = ['reason', 'purchase__id', 'shop__name']
    ordering_fields = ['created_at', 'return_date']
    ordering = ['-created_at']
    pagination_class = CustomPagination

    def get_queryset(self):
        if user_can_see_all_shop_data(self.request.user, self.required_tab):
            queryset = PurchaseReturn.objects.all()
        else:
            shop_access_filter = Q(shop__owner=self.request.user) | Q(
                shop__assigned_users=self.request.user
            )
            queryset = PurchaseReturn.objects.filter(
                shop_access_filter & (Q(created_by=self.request.user) | Q(created_by__isnull=True))
            ).distinct()
            
        start_date = self.request.query_params.get("start_date")
        end_date = self.request.query_params.get("end_date")
        if start_date:
            queryset = queryset.filter(return_date__date__gte=start_date)
        if end_date:
            queryset = queryset.filter(return_date__date__lte=end_date)
        return queryset.order_by("-id")

    def create(self, request, *args, **kwargs):
        with transaction.atomic():
            serializer = self.get_serializer(data=request.data)
            serializer.is_valid(raise_exception=True)

            purchase_id = serializer.validated_data["purchase"].id
            purchase = serializer.validated_data["purchase"]
            shop_id = serializer.validated_data["shop"].id
            uses_inventory_engine = bool(purchase.uses_inventory_engine and purchase.warehouse_id)
            seen_purchase_items = set()
            for item_data in serializer.validated_data.get("return_items", []):
                purchase_item = item_data["purchase_item"]
                return_quantity = int(item_data["return_quantity"] or 0)
                if purchase_item.id in seen_purchase_items:
                    raise ValidationError(
                        {"return_items": "A purchase item can appear only once per return."}
                    )
                seen_purchase_items.add(purchase_item.id)
                if (
                    purchase_item.purchase_id != purchase_id
                    or purchase_item.purchase.shop_id != shop_id
                ):
                    raise ValidationError(
                        {"return_items": "Purchase item does not belong to this purchase and branch."}
                    )
                already_returned = (
                    PurchaseReturnItem.objects.filter(purchase_item=purchase_item)
                    .exclude(purchase_return__document_status=PurchaseReturn.DocumentStatus.VOID)
                    .exclude(
                        purchase_return__uses_inventory_engine=True,
                        purchase_return__document_status=PurchaseReturn.DocumentStatus.DRAFT,
                    )
                    .aggregate(total=Sum("return_quantity"))["total"]
                    or 0
                )
                if int(already_returned) + return_quantity > int(purchase_item.quantity or 0):
                    raise ValidationError(
                        {
                            "return_items": (
                                f"Returned quantity for {purchase_item.product.name} "
                                "exceeds purchased quantity."
                            )
                        }
                    )

            purchase_return = serializer.save(
                uses_inventory_engine=uses_inventory_engine,
                document_status=(
                    PurchaseReturn.DocumentStatus.DRAFT
                    if uses_inventory_engine
                    else PurchaseReturn.DocumentStatus.POSTED
                ),
            )

            if uses_inventory_engine:
                headers = self.get_success_headers(serializer.data)
                return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

            for return_item in purchase_return.return_items.select_related(
                "purchase_item",
                "purchase_item__product",
                "purchase_item__purchase",
            ):
                purchase_item = return_item.purchase_item
                stocks = Stock.objects.select_for_update().filter(
                    shop=purchase_return.shop,
                    purchase_item__product_id=purchase_item.product_id,
                    is_deleted=False,
                    purchase_item__is_deleted=False,
                    purchase_item__purchase__is_deleted=False,
                )
                currency_id = purchase_item.currency_id or purchase_item.purchase.currency_id
                if currency_id:
                    stocks = stocks.filter(
                        Q(purchase_item__currency_id=currency_id)
                        | Q(
                            purchase_item__currency__isnull=True,
                            purchase_item__purchase__currency_id=currency_id,
                        )
                    )
                stock_rows = list(stocks.order_by("-quantity", "id"))
                quantity_to_remove = int(return_item.return_quantity or 0)
                if sum(int(row.quantity or 0) for row in stock_rows) < quantity_to_remove:
                    raise ValidationError(
                        {
                            "return_items": (
                                f"Not enough stock to return {purchase_item.product.name} "
                                "to the supplier."
                            )
                        }
                    )
                for stock in stock_rows:
                    if quantity_to_remove <= 0:
                        break
                    available = int(stock.quantity or 0)
                    removed = min(available, quantity_to_remove)
                    stock.quantity = available - removed
                    stock.save(update_fields=["quantity", "updated_at"])
                    quantity_to_remove -= removed

            headers = self.get_success_headers(serializer.data)
            return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

    def update(self, request, *args, **kwargs):
        instance = self.get_object()
        if instance.uses_inventory_engine and instance.document_status == PurchaseReturn.DocumentStatus.DRAFT:
            return super().update(request, *args, **kwargs)
        return Response(
            {
                "detail": (
                    "Purchase returns cannot be edited because that can corrupt stock. "
                    "Delete the return and create it again."
                )
            },
            status=status.HTTP_405_METHOD_NOT_ALLOWED,
        )

    def destroy(self, request, *args, **kwargs):
        with transaction.atomic():
            instance = self.get_object()
            if instance.uses_inventory_engine:
                if instance.document_status != PurchaseReturn.DocumentStatus.DRAFT:
                    return Response(
                        {"detail": "Posted or cancelled purchase returns cannot be deleted."},
                        status=status.HTTP_400_BAD_REQUEST,
                    )
                self.perform_destroy(instance)
                return Response(status=status.HTTP_204_NO_CONTENT)
            
            # Add returned quantities back to stock when deleting purchase return
            for return_item in instance.return_items.all():
                purchase_item = return_item.purchase_item
                stocks = Stock.objects.select_for_update().filter(
                    shop=instance.shop,
                    purchase_item__product_id=purchase_item.product_id,
                    is_deleted=False,
                    purchase_item__is_deleted=False,
                    purchase_item__purchase__is_deleted=False,
                )
                currency_id = purchase_item.currency_id or purchase_item.purchase.currency_id
                if currency_id:
                    stocks = stocks.filter(
                        Q(purchase_item__currency_id=currency_id)
                        | Q(
                            purchase_item__currency__isnull=True,
                            purchase_item__purchase__currency_id=currency_id,
                        )
                    )
                stock = stocks.filter(purchase_item=purchase_item).first() or stocks.first()
                if stock is None:
                    raise ValidationError(
                        {
                            "detail": (
                                f"Stock record not found for {purchase_item.product.name}."
                            )
                        }
                    )
                stock.quantity = int(stock.quantity or 0) + int(
                    return_item.return_quantity or 0
                )
                stock.save(update_fields=["quantity", "updated_at"])

            self.perform_destroy(instance)
            return Response(status=status.HTTP_204_NO_CONTENT)

    @action(detail=True, methods=["post"])
    def post(self, request, pk=None):
        instance = self.get_object()
        try:
            result = post_purchase_return(instance.pk, user=request.user)
        except DjangoValidationError as exc:
            raise ValidationError(exc.message_dict if hasattr(exc, "message_dict") else exc.messages)
        return Response(self.get_serializer(result).data)

    @action(detail=True, methods=["post"])
    def void(self, request, pk=None):
        instance = self.get_object()
        try:
            result = void_purchase_return(
                instance.pk, user=request.user, reason=request.data.get("reason", "")
            )
        except DjangoValidationError as exc:
            raise ValidationError(exc.message_dict if hasattr(exc, "message_dict") else exc.messages)
        return Response(self.get_serializer(result).data)
