from rest_framework import status
from decimal import Decimal
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
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 Sum
from account.permissions import (
    HasTabPermission, HasActionPermission, assert_shop_access,
    user_can_see_all_shop_data,
    get_user_data_filter,
)
from shop_management.views.data.base import DataRootViewSet
from shop_management.models.data.sale_return import SaleReturn, SaleReturnItem
from shop_management.models.data.sale import SaleItem
from shop_management.models.data.stock import Stock
from shop_management.serializers.data.sale_return import SaleReturnSerializer
from shop_management.models.data.sale import Sale
from shop_management.services.sale_returns import post_sale_return, void_sale_return


class SaleReturnViewSet(DataRootViewSet):
    permission_classes = [IsAuthenticated, HasTabPermission, HasActionPermission]
    action_permission_map = {"create": "sales.return_sale", "post": "sales.return_sale", "void": "sales.void_sale"}
    required_tab = "sale_return"
    serializer_class = SaleReturnSerializer

    def get_queryset(self):
        if user_can_see_all_shop_data(self.request.user, self.required_tab):
            queryset = SaleReturn.objects.all()
        else:
            queryset = SaleReturn.objects.filter(get_user_data_filter(self.request.user, "shop")).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)

        shop = self.request.query_params.get("shop")
        if shop:
            queryset = queryset.filter(shop_id=shop)

        return queryset.order_by("-id")

    def create(self, request, *args, **kwargs):
        data = request.data

        try:
            with transaction.atomic():
                sale_id = data.get("sale")
                shop_id = data.get("shop")
                return_items = data.get("return_items", [])
                if not sale_id or not shop_id or not return_items:
                    raise ValidationError("Sale, branch, and return items are required.")

                shop = assert_shop_access(request.user, shop_id, request)
                sale = Sale.objects.select_for_update().filter(pk=sale_id, shop=shop).first()
                if not sale:
                    raise ValidationError("Sale does not belong to the selected branch.")
                if sale.uses_inventory_engine and sale.document_status != Sale.DocumentStatus.POSTED:
                    raise ValidationError("Only posted sales can be returned.")
                if sale.uses_inventory_engine and sale.warehouse_id:
                    if sale.shop_id != int(shop_id):
                        raise ValidationError("Sale does not belong to the selected branch.")
                    sale_return = SaleReturn.objects.create(
                        shop_id=shop_id, sale=sale, reason=data.get("reason", ""),
                        refund_account_id=data.get("refund_account") or None,
                        cash_refund_amount=data.get("cash_refund_amount") or 0,
                        uses_inventory_engine=True, document_status=SaleReturn.DocumentStatus.DRAFT,
                        created_by=request.user,
                    )
                    seen = set()
                    for item_data in return_items:
                        sale_item = SaleItem.objects.select_for_update().select_related("product").get(pk=item_data.get("sale_item"))
                        quantity = int(item_data.get("return_quantity", 0))
                        if sale_item.id in seen or sale_item.sale_id != sale.id:
                            raise ValidationError("Each return item must be unique and belong to the sale.")
                        if quantity <= 0:
                            raise ValidationError("Return quantity must be greater than zero.")
                        seen.add(sale_item.id)
                        SaleReturnItem.objects.create(
                            sale_return=sale_return, sale_item=sale_item,
                            return_quantity=quantity, refund_amount=0, created_by=request.user,
                        )
                    return Response(self.get_serializer(sale_return).data, status=201)

                sale_return = SaleReturn.objects.create(
                    shop_id=shop_id,
                    sale_id=sale_id,
                    reason=data.get("reason", ""),
                    document_status=SaleReturn.DocumentStatus.POSTED,
                    created_by=request.user,
                )

                total_refund = 0
                stock_updates = []

                for item_data in return_items:
                    sale_item = (
                        SaleItem.objects.select_for_update()
                        .select_related("sale", "product", "purchase_item")
                        .get(id=item_data.get("sale_item"))
                    )
                    if sale_item.sale_id != int(sale_id) or sale_item.sale.shop_id != int(shop_id):
                        raise ValidationError("Return item does not belong to the selected sale and branch.")

                    return_quantity = int(item_data.get("return_quantity", 0))
                    if return_quantity <= 0:
                        raise ValidationError("Return quantity must be greater than zero.")

                    already_returned = (
                        SaleReturnItem.objects.filter(sale_item=sale_item)
                        .aggregate(total=Sum("return_quantity"))["total"]
                        or 0
                    )
                    if int(already_returned) + return_quantity > int(sale_item.quantity or 0):
                        raise ValidationError(
                            f"Returned quantity for {sale_item.product.name} exceeds sold quantity."
                        )

                    refund_amount = item_data.get("refund_amount")
                    if refund_amount in (None, ""):
                        refund_amount = sale_item.sale_price * return_quantity
                    return_item = SaleReturnItem.objects.create(
                        sale_return=sale_return,
                        sale_item=sale_item,
                        return_quantity=return_quantity,
                        refund_amount=refund_amount,
                        created_by=request.user,
                    )
                    total_refund += Decimal(str(return_item.refund_amount or 0))

                    stocks = Stock.objects.select_for_update().filter(
                        shop_id=shop_id,
                        is_deleted=False,
                    )
                    stock = None
                    if sale_item.purchase_item_id:
                        stock = stocks.filter(
                            purchase_item_id=sale_item.purchase_item_id
                        ).first()
                    if stock is None and sale_item.product_id:
                        stock = stocks.filter(
                            purchase_item__product_id=sale_item.product_id
                        ).first()
                    if stock is None:
                        raise ValidationError(
                            f"Stock record not found for {sale_item.product.name}."
                        )

                    old_qty = int(stock.quantity or 0)
                    stock.quantity = old_qty + return_quantity
                    stock.save(update_fields=["quantity", "updated_at"])
                    stock_updates.append({
                        "product": sale_item.product.name,
                        "before": old_qty,
                        "returned": return_quantity,
                        "after": stock.quantity,
                    })

                sale_return.total_refound_amount = total_refund
                sale_return.save(update_fields=["total_refound_amount", "updated_at"])

            return Response({
                "message": "Return created successfully",
                "id": sale_return.id,
                "stock_updates": stock_updates
            }, status=201)
        except Exception as e:
            return Response({"error": str(e)}, status=400)

    def destroy(self, request, *args, **kwargs):
        with transaction.atomic():
            instance = self.get_object()

            if instance.uses_inventory_engine:
                if instance.document_status != SaleReturn.DocumentStatus.DRAFT:
                    raise ValidationError("Posted or cancelled sale returns cannot be deleted.")
                self.perform_destroy(instance)
                return Response(status=status.HTTP_204_NO_CONTENT)

            # Remove returned quantities from stock when deleting sale return
            for return_item in instance.return_items.select_related(
                "sale_item", "sale_item__product"
            ):
                if not return_item.sale_item or not return_item.sale_item.product_id:
                    raise ValidationError("Returned sale item is no longer available.")

                stocks = Stock.objects.select_for_update().filter(
                    shop=instance.shop,
                    is_deleted=False,
                )
                stock = None
                if return_item.sale_item.purchase_item_id:
                    stock = stocks.filter(
                        purchase_item_id=return_item.sale_item.purchase_item_id
                    ).first()
                if stock is None:
                    stock = stocks.filter(
                        purchase_item__product_id=return_item.sale_item.product_id
                    ).first()
                if stock is None:
                    raise ValidationError(
                        f"Stock record not found for {return_item.sale_item.product.name}."
                    )
                if int(stock.quantity or 0) < int(return_item.return_quantity or 0):
                    raise ValidationError(
                        f"Cannot delete return for {return_item.sale_item.product.name}: "
                        "returned stock has already been used."
                    )

                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_sale_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_sale_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)
