"""
Granular mixins for bulk operations.

Provides optional mixins for adding specific bulk operations to views.
Can be used independently of BulkModelViewSet.
"""

from rest_framework import status
from rest_framework.response import Response

from .operations import BulkDeleteOperation
from .validators import validate_bulk_request, validate_for_delete
from .settings import bulk_settings
from .transactions import BulkTransactionManager


class BulkCreateMixin:
    """
    Adds bulk create capability to a view.
    Can be used independently of BulkModelViewSet.
    """

    def create(self, request, *args, **kwargs):
        """
        Override create to handle bulk.
        Detects list vs. single object automatically.

        Args:
            request: HTTP request
            *args: Positional arguments
            **kwargs: Keyword arguments

        Returns:
            Response
        """
        is_bulk = isinstance(request.data, list)

        if not is_bulk:
            # Standard single create
            return super().create(request, *args, **kwargs)

        # Bulk create
        return self._bulk_create(request)

    def _bulk_create(self, request):
        """
        Handle bulk create.

        Args:
            request: HTTP request

        Returns:
            Response
        """
        # Validate batch size
        validate_bulk_request(request.data, getattr(self, "unique_fields", ["id"]), bulk_settings.max_batch_size)

        # Get serializer with many=True
        serializer = self.get_serializer(data=request.data, many=True)
        serializer.is_valid(raise_exception=True)

        # Execute with transaction
        transaction_manager = BulkTransactionManager(
            atomic=bulk_settings.atomic_operations, failure_strategy=bulk_settings.partial_failure_strategy
        )

        with transaction_manager.execute():
            self.perform_create(serializer)

        # Format response
        instances = serializer.instance if isinstance(serializer.instance, list) else [serializer.instance]

        data = {"created": len(instances), "updated": 0, "failed": 0, "data": serializer.data}

        return Response(data, status=status.HTTP_201_CREATED)


class BulkUpdateMixin:
    """
    Adds bulk update capability.
    PUT on collection endpoint.
    """

    def update(self, request, *args, **kwargs):
        """
        Override update to handle bulk.
        Requires unique_fields in each object.

        Args:
            request: HTTP request
            *args: Positional arguments
            **kwargs: Keyword arguments

        Returns:
            Response
        """
        # Check if detail endpoint
        if self.kwargs.get(self.lookup_field):
            return super().update(request, *args, **kwargs)

        # Check if bulk request
        if not isinstance(request.data, list):
            from rest_framework.exceptions import ValidationError

            raise ValidationError("Bulk update requires a list of objects")

        return self._bulk_update(request, partial=False)

    def _bulk_update(self, request, partial=False):
        """
        Handle bulk update.

        Args:
            request: HTTP request
            partial: Whether partial update

        Returns:
            Response
        """
        # Get serializer with many=True
        serializer = self.get_serializer(data=request.data, many=True, partial=partial)
        serializer.is_valid(raise_exception=True)

        # Execute update
        transaction_manager = BulkTransactionManager(
            atomic=bulk_settings.atomic_operations, failure_strategy=bulk_settings.partial_failure_strategy
        )

        with transaction_manager.execute():
            if hasattr(serializer, "update"):
                instances = serializer.update(serializer.validated_data)
            else:
                instances = serializer.save()

        # Format response
        instances = instances if isinstance(instances, list) else [instances]
        response_serializer = self.get_serializer(instances, many=True)

        data = {"created": 0, "updated": len(instances), "failed": 0, "data": response_serializer.data}

        return Response(data, status=status.HTTP_200_OK)


class BulkUpsertMixin:
    """
    Adds bulk upsert capability.
    PATCH on collection endpoint.
    """

    def partial_update(self, request, *args, **kwargs):
        """
        Override partial_update to handle bulk upsert.
        Creates or updates based on unique_fields.

        Args:
            request: HTTP request
            *args: Positional arguments
            **kwargs: Keyword arguments

        Returns:
            Response
        """
        # Check if detail endpoint
        if self.kwargs.get(self.lookup_field):
            return super().partial_update(request, *args, **kwargs)

        # Check if bulk request
        if not isinstance(request.data, list):
            from rest_framework.exceptions import ValidationError

            raise ValidationError("Bulk upsert requires a list of objects")

        return self._bulk_upsert(request)

    def _bulk_upsert(self, request):
        """
        Handle bulk upsert.

        Args:
            request: HTTP request

        Returns:
            Response
        """
        # Get serializer with many=True
        serializer = self.get_serializer(data=request.data, many=True, partial=True)
        serializer.is_valid(raise_exception=True)

        # Execute upsert
        transaction_manager = BulkTransactionManager(
            atomic=bulk_settings.atomic_operations, failure_strategy=bulk_settings.partial_failure_strategy
        )

        with transaction_manager.execute():
            if hasattr(serializer, "upsert"):
                instances = serializer.upsert(serializer.validated_data)
            else:
                instances = serializer.save()

        # Format response
        instances = instances if isinstance(instances, list) else [instances]
        response_serializer = self.get_serializer(instances, many=True)

        data = {
            "created": 0,  # Would need operation result to separate
            "updated": len(instances),
            "failed": 0,
            "data": response_serializer.data,
        }

        return Response(data, status=status.HTTP_200_OK)


class BulkDestroyMixin:
    """
    Adds bulk delete capability.
    DELETE on collection endpoint.
    """

    def destroy(self, request, *args, **kwargs):
        """
        Override destroy to handle bulk.
        Payload contains unique_field identifiers.

        Args:
            request: HTTP request
            *args: Positional arguments
            **kwargs: Keyword arguments

        Returns:
            Response
        """
        # Check if detail endpoint
        if self.kwargs.get(self.lookup_field):
            return super().destroy(request, *args, **kwargs)

        # Check if bulk request
        if not isinstance(request.data, list):
            from rest_framework.exceptions import ValidationError

            raise ValidationError("Bulk delete requires a list of objects with unique field identifiers")

        return self._bulk_destroy(request)

    def _bulk_destroy(self, request):
        """
        Handle bulk destroy.

        Args:
            request: HTTP request

        Returns:
            Response
        """
        unique_fields = getattr(self, "unique_fields", ["id"])

        # Validate delete request
        validate_for_delete(request.data, unique_fields)

        # Execute delete operation
        model = self.get_queryset().model
        batch_size = getattr(self, "batch_size", bulk_settings.default_batch_size)

        operation = BulkDeleteOperation(
            model=model, unique_fields=unique_fields, batch_size=batch_size, context={"request": request, "view": self}
        )

        transaction_manager = BulkTransactionManager(
            atomic=bulk_settings.atomic_operations, failure_strategy=bulk_settings.partial_failure_strategy
        )

        with transaction_manager.execute():
            result = operation.execute(request.data)

        # Format response
        data = {"deleted": result.deleted}

        return Response(data, status=status.HTTP_200_OK)
