"""
Printing API ViewSet
"""

from rest_framework import viewsets, filters, status
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError as DRFValidationError
from rest_framework.response import Response
from rest_framework.permissions import BasePermission
from rest_framework.permissions import DjangoModelPermissions
from rest_framework.parsers import MultiPartParser, FormParser, JSONParser
from django.core.exceptions import ValidationError
from django.db.models import CharField, Exists, OuterRef, Prefetch, Subquery
from django.db.models.functions import Cast, Coalesce
from django.views.decorators.cache import cache_page
from django.views.decorators.http import condition
from django.utils.decorators import method_decorator
from django.utils import timezone
from django.utils.dateparse import parse_date, parse_datetime
from django_filters.rest_framework import DjangoFilterBackend
from django_filters import rest_framework as django_filters
from django_filters import IsoDateTimeFilter
from datetime import datetime, time, timedelta

from flower.viewsets import LimitedModelViewSet
from printing import models
from basic_info import models as basic_models
from basic_info.models import MerchantTypeEnum

from .serializers import (
    PrintingOrderListSerializer,
    PrintingOrderDetailSerializer,
    PrintingOrderCreateUpdateSerializer,
    PrintingJobListSerializer,
    PrintingJobDetailSerializer,
    PrintingJobCreateUpdateSerializer,
)
from .mixins import CustomerVisibilityFilterMixin


class IsPrintingFactory(BasePermission):
    """自定义权限类，允许印染工厂用户访问"""

    message = "您没有访问印染订单的权限"

    def has_permission(self, request, view):
        if not request.user.is_authenticated:
            return False
        if hasattr(request.user, "employee"):
            return request.user.employee.merchant.type == MerchantTypeEnum.FACTORY
        return False


class HasInvalidatePrintingOrderPermission(BasePermission):
    """自定义权限类，检查用户是否有作废印染订单的权限"""

    message = "您没有权限作废订单"

    def has_permission(self, request, view):
        if not request.user.is_authenticated:
            return False
        return request.user.has_perm("printing.can_invalidate_printingorder")


class HasActivatePrintingOrderPermission(BasePermission):
    """自定义权限类，检查用户是否有恢复印染订单的权限"""

    message = "您没有权限恢复订单"

    def has_permission(self, request, view):
        if not request.user.is_authenticated:
            return False
        return request.user.has_perm("printing.can_activate_printingorder")


class PrintingOrderFilterSet(django_filters.FilterSet):
    """印染订单过滤器"""

    external_order_id = django_filters.CharFilter(lookup_expr="icontains")
    customer_name = django_filters.CharFilter(
        field_name="customer__name", lookup_expr="icontains"
    )
    customer_phone = django_filters.CharFilter(
        field_name="customer__phone", lookup_expr="icontains"
    )
    fabric = django_filters.CharFilter(lookup_expr="icontains")
    is_urgent = django_filters.BooleanFilter()
    is_fabric_received = django_filters.BooleanFilter()
    is_invalid = django_filters.BooleanFilter()
    area = django_filters.CharFilter(lookup_expr="icontains")
    outgoing_date_from = django_filters.CharFilter(method="filter_outgoing_date_from")
    outgoing_date_to = django_filters.CharFilter(method="filter_outgoing_date_to")
    created_date_from = django_filters.DateFilter(
        field_name="created_at", lookup_expr="gte"
    )
    created_date_to = django_filters.DateFilter(
        field_name="created_at", method="filter_created_date_to"
    )
    # 支持同时搜索 human_id 和 external_order_id
    order_number = django_filters.CharFilter(method="filter_order_number")

    class Meta:
        model = models.PrintingOrder
        fields = [
            "customer",
            "is_urgent",
            "is_fabric_received",
            "is_invalid",
            "external_order_id",
            "order_number",
        ]

    def filter_order_number(self, queryset, name, value):
        """
        按订单号过滤（支持 human_id 和 external_order_id）

        同时搜索：
        1. external_order_id（模糊匹配）
        2. id（从 human_id 格式年月日+ID 中提取）

        例如：order_number=20260323001505
        - 匹配 external_order_id 包含 "20260323001505"
        - 匹配 id=1505（提取最后6位）
        """
        if not value:
            return queryset

        from django.db.models import Q

        # 构建查询条件
        q_objects = Q(external_order_id__icontains=value)

        # 尝试从 human_id 格式提取 ID
        # human_id 格式：年月日 + ID（如：20260323001505）
        if len(value) >= 6:
            # 尝试提取最后6位作为 ID
            id_part = value[-6:].lstrip("0")  # 去除前导零
            if id_part.isdigit():
                q_objects |= Q(id=int(id_part))

        # 如果 value 是纯数字，也尝试直接匹配 ID
        if value.isdigit():
            q_objects |= Q(id=int(value))

        return queryset.filter(q_objects)

    @staticmethod
    def _parse_date_or_datetime(value: str | None) -> tuple[datetime | None, bool]:
        """
        解析日期/日期时间字符串为“当前时区”的 aware datetime。

        返回：
        - (dt, has_time)
        - has_time=True：输入包含时间（datetime）
        - has_time=False：输入为纯日期（date-only）
        """
        s = (value or "").strip()
        if not s:
            return None, False

        tz = timezone.get_current_timezone()

        # 注意：Django 的 parse_datetime 在某些版本会把纯日期（YYYY-MM-DD）也解析成 00:00:00，
        # 这会让“to=日期”语义从“包含整天”退化为“<= 当天 00:00:00”。因此这里先尝试 parse_date。
        d = parse_date(s)
        if d is not None:
            dt0 = timezone.make_aware(datetime.combine(d, time.min), tz)
            return dt0, False

        dt = parse_datetime(s)
        if dt is not None:
            if timezone.is_naive(dt):
                dt = timezone.make_aware(dt, tz)
            else:
                dt = timezone.localtime(dt, tz)
            return dt, True

        return None, False

    def filter_outgoing_date_from(self, queryset, name, value):
        dt, _has_time = self._parse_date_or_datetime(value)
        if dt is None:
            # 与 DRF/DjangoFilterBackend 行为保持一致：参数非法返回 400
            raise ValidationError("outgoing_date_from 必须是日期或日期时间")
        return queryset.filter(outgoing_date__gte=dt)

    def filter_outgoing_date_to(self, queryset, name, value):
        dt, has_time = self._parse_date_or_datetime(value)
        if dt is None:
            raise ValidationError("outgoing_date_to 必须是日期或日期时间")

        # 纯日期：包含整天 => outgoing_date < 次日 00:00:00
        if not has_time:
            return queryset.filter(outgoing_date__lt=dt + timedelta(days=1))

        # 日期时间：按“<=”包含到秒
        return queryset.filter(outgoing_date__lte=dt)

    def filter_created_date_to(self, queryset, name, value):
        """
        过滤 created_at <= 指定日期的 23:59:59.999999
        使用 lt 次日 00:00:00 实现，确保包含整天
        """
        if value:
            from datetime import timedelta

            next_day = value + timedelta(days=1)
            return queryset.filter(created_at__lt=next_day)
        return queryset


@method_decorator(cache_page(20), name="list")
class PrintingOrderViewSet(CustomerVisibilityFilterMixin, LimitedModelViewSet):
    """
    印染订单 ViewSet

    提供印染订单的增改查功能（不支持删除）

    list: 获取印染订单列表
    retrieve: 获取印染订单详情
    create: 创建印染订单
    update: 更新印染订单
    partial_update: 部分更新印染订单

    查询参数:
    - customer: 客户ID
    - external_order_id: 外部订单编号（模糊查询）
    - customer_name: 客户名称（模糊查询）
    - customer_phone: 客户电话（模糊查询）
    - fabric: 面料（模糊查询）
    - is_urgent: 是否紧急（true/false）
    - is_fabric_received: 布料是否已收（true/false）
    - is_invalid: 是否作废（true/false）
    - area: 地区（模糊查询）
    - outgoing_date_from: 出货日期起始
    - outgoing_date_to: 出货日期结束
    - created_date_from: 创建日期起始
    - created_date_to: 创建日期结束
    - search: 全文搜索（客户名称、面料、地区、工艺）
    - ordering: 排序字段

    返回字段说明:
    - jobs_status_summary: 订单下所有印染任务按下一待执行状态分组的数量汇总
      格式: [{"state_name": "进度一", "state_id": 1, "count": 3}, ...]
    - jobs_last_status_summary: 订单下所有印染任务按最后完成状态分组的数量汇总
      格式: [{"state_name": "进度一", "count": 3}, ...]

    权限控制:
    - 默认按客户可见性过滤（员工只能看到自己负责的客户的订单）
    - 拥有 printing.view_all_printingorders 权限可突破此限制
    """

    queryset = models.PrintingOrder.objects.all()
    customer_field = "customer"
    view_all_permission = "printing.view_all_printingorders"
    permission_classes = [DjangoModelPermissions]
    filter_backends = [
        DjangoFilterBackend,
        filters.SearchFilter,
        filters.OrderingFilter,
    ]
    filterset_class = PrintingOrderFilterSet
    search_fields = ["customer__name", "fabric", "area", "craft", "description"]
    ordering_fields = [
        "id",
        "created_at",
        "updated_at",
        "outgoing_date",
        "is_urgent",
        "is_fabric_received",
        "is_invalid",
    ]
    ordering = ["-created_at"]

    def get_permissions(self):
        permissions = super().get_permissions() + [IsPrintingFactory()]
        if self.action == "invalidate":
            permissions.append(HasInvalidatePrintingOrderPermission())
        elif self.action == "activate":
            permissions.append(HasActivatePrintingOrderPermission())
        return permissions

    def get_serializer_class(self):
        """根据动作选择序列化器"""
        if self.action == "list":
            return PrintingOrderListSerializer
        elif self.action in ["create", "update", "partial_update"]:
            return PrintingOrderCreateUpdateSerializer
        else:  # retrieve
            return PrintingOrderDetailSerializer

    def get_queryset(self):
        """优化查询"""
        queryset = super().get_queryset()

        # 默认不返回作废订单（仅 list）；如显式传入 is_invalid 参数，则交由 FilterSet 处理
        # 注意：不能影响 activate/invalidate 等 action，否则会因 get_object() 被过滤而 404。
        if (
            self.action == "list"
            and self.request
            and "is_invalid" not in self.request.query_params
        ):
            queryset = queryset.filter(is_invalid=False)

        if self.action == "list":
            customer_qs = basic_models.Customer.objects.filter(id=OuterRef("customer_id"))
            queryset = queryset.annotate(
                customer_name=Subquery(customer_qs.values("name")[:1]),
                customer_phone=Subquery(customer_qs.values("mobile")[:1]),
            )
        elif self.action in ["retrieve", "by_external_order_id"]:
            queryset = queryset.select_related("customer")

        if self.action in ["list", "retrieve", "by_external_order_id"]:
            # 预取 printing_jobs 及其 business_object，用于状态汇总统计
            # 同时预取 state_logs 和 process 相关数据以减少 job.status 属性调用时的 N+1 查询
            queryset = queryset.prefetch_related(
                Prefetch(
                    "printing_jobs",
                    queryset=models.PrintingJob.objects.select_related(
                        "business_object",
                        "business_object__process",
                    ).prefetch_related(
                        "business_object__state_logs",
                        "business_object__process__process_nodes",
                        "business_object__process__process_nodes__state",
                    ),
                )
            )
        return queryset

    def destroy(self, request, *args, **kwargs):
        """禁用删除操作"""
        return Response(
            {"detail": "印染订单不支持删除操作，请使用作废功能"},
            status=status.HTTP_405_METHOD_NOT_ALLOWED,
        )

    @action(
        detail=False,
        methods=["get"],
        url_path=r"by-external-order-id/(?P<external_order_id>[^/.]+)",
    )
    def by_external_order_id(self, request, external_order_id=None):
        """通过 external_order_id 获取印染订单详情"""
        normalized_external_order_id = str(external_order_id or "").strip()
        printing_order = (
            self.get_queryset()
            .filter(external_order_id=normalized_external_order_id)
            .order_by("id")
            .first()
        )
        if not printing_order:
            return Response(
                {"detail": "未找到该 external_order_id 对应的印染订单"},
                status=status.HTTP_404_NOT_FOUND,
            )

        serializer = self.get_serializer(printing_order)
        return Response(serializer.data)

    @action(detail=True, methods=["get"], url_path="ids")
    def ids(self, request, pk=None):
        """返回该印染订单下所有 PrintingJob ID。"""
        printing_order = self.get_object()
        ids = list(
            printing_order.printing_jobs.order_by("id").values_list("id", flat=True)
        )
        return Response({"ids": ids})

    @action(detail=True, methods=["post"])
    def invalidate(self, request, pk=None):
        """
        作废订单

        需要权限: printing.can_invalidate_printingorder
        """
        # 检查权限
        if not request.user.has_perm("printing.can_invalidate_printingorder"):
            return Response(
                {"detail": "您没有权限作废订单"}, status=status.HTTP_403_FORBIDDEN
            )

        printing_order = self.get_object()

        if printing_order.is_invalid:
            return Response(
                {"detail": "该订单已经作废"}, status=status.HTTP_400_BAD_REQUEST
            )

        printing_order.is_invalid = True
        printing_order.save()

        serializer = PrintingOrderDetailSerializer(printing_order)
        return Response({"detail": "订单已作废", "data": serializer.data})

    @action(detail=True, methods=["post"])
    def activate(self, request, pk=None):
        """
        恢复订单

        需要权限: printing.can_activate_printingorder
        """
        # 检查权限
        if not request.user.has_perm("printing.can_activate_printingorder"):
            return Response(
                {"detail": "您没有权限恢复订单"}, status=status.HTTP_403_FORBIDDEN
            )

        printing_order = self.get_object()

        if not printing_order.is_invalid:
            return Response(
                {"detail": "该订单未作废，无需恢复"}, status=status.HTTP_400_BAD_REQUEST
            )

        printing_order.is_invalid = False
        printing_order.save()

        serializer = PrintingOrderDetailSerializer(printing_order)
        return Response({"detail": "订单已恢复", "data": serializer.data})

    @action(detail=True, methods=["post"])
    def mark_fabric_received(self, request, pk=None):
        """标记布料已收"""
        printing_order = self.get_object()

        if printing_order.is_fabric_received:
            return Response(
                {"detail": "布料已经标记为已收"}, status=status.HTTP_400_BAD_REQUEST
            )

        printing_order.is_fabric_received = True
        printing_order.save()

        serializer = PrintingOrderDetailSerializer(printing_order)
        return Response({"detail": "已标记布料已收", "data": serializer.data})


class PrintingJobFilterSet(django_filters.FilterSet):
    """印染款式明细过滤器"""

    printing_order = django_filters.NumberFilter()
    external_order_id = django_filters.CharFilter(
        field_name="printing_order__external_order_id", lookup_expr="icontains"
    )
    customer_name = django_filters.CharFilter(
        field_name="printing_order__customer__name", lookup_expr="icontains"
    )
    product = django_filters.NumberFilter()
    product_name = django_filters.CharFilter(
        field_name="product__name", lookup_expr="icontains"
    )
    unit = django_filters.CharFilter(lookup_expr="icontains")
    quantity_min = django_filters.NumberFilter(field_name="quantity", lookup_expr="gte")
    quantity_max = django_filters.NumberFilter(field_name="quantity", lookup_expr="lte")
    pieces_min = django_filters.NumberFilter(field_name="pieces", lookup_expr="gte")
    pieces_max = django_filters.NumberFilter(field_name="pieces", lookup_expr="lte")
    created_at_from = django_filters.CharFilter(method="filter_created_at_from")
    created_at_to = django_filters.CharFilter(method="filter_created_at_to")
    is_production_completed = django_filters.BooleanFilter()

    class Meta:
        model = models.PrintingJob
        fields = ["printing_order", "product", "external_order_id", "is_production_completed"]

    def filter_created_at_from(self, queryset, name, value):
        dt, _has_time = PrintingOrderFilterSet._parse_date_or_datetime(value)
        if dt is None:
            raise ValidationError("created_at_from 必须是日期或日期时间")
        return queryset.filter(created_at__gte=dt)

    def filter_created_at_to(self, queryset, name, value):
        dt, has_time = PrintingOrderFilterSet._parse_date_or_datetime(value)
        if dt is None:
            raise ValidationError("created_at_to 必须是日期或日期时间")

        if not has_time:
            return queryset.filter(created_at__lt=dt + timedelta(days=1))

        return queryset.filter(created_at__lte=dt)


class PrintingJobViewSet(CustomerVisibilityFilterMixin, LimitedModelViewSet):
    """
    印染款式明细 ViewSet

    提供印染款式明细的增改查功能（不支持删除）

    list: 获取款式明细列表
    retrieve: 获取款式明细详情
    create: 创建款式明细
    update: 更新款式明细
    partial_update: 部分更新款式明细
    advance_to_next_state: 推进到下一个状态
    step_back_one_state: 回退一步
    completed_states: 查询已完成的流程列表
    timeline: 获取流程时间线

    注意：状态流转接口是对 stateflow 模块的便捷包装
    - 前端只需提供 printing_job 的 ID
    - 系统会自动查找关联的 business_object 并进行状态流转
    - 也可以直接使用 stateflow 的原始接口（需要 business_object_id）

    查询参数:
    - printing_order: 印染订单ID
    - external_order_id: 外部订单编号（按所属订单模糊查询）
    - customer_name: 客户名称（按所属订单客户名称模糊查询）
    - product: 产品ID
    - product_name: 产品名称（模糊查询）
    - unit: 单位（模糊查询）
    - quantity_min: 最小数量
    - quantity_max: 最大数量
    - pieces_min: 最小件数
    - pieces_max: 最大件数
    - created_at_from: 创建时间开始（支持 YYYY-MM-DD 或日期时间）
    - created_at_to: 创建时间结束（YYYY-MM-DD 会包含整天）
    - is_production_completed: 是否已标记完成生产（true/false）
    - search: 全文搜索（产品名称、单位、尺寸、备注）
    - ordering: 排序字段

    权限控制:
    - 默认按客户可见性过滤（通过 printing_order.customer）
    - 拥有 printing.view_all_printingorders 权限可突破此限制
    """

    queryset = models.PrintingJob.objects.all()
    customer_field = "printing_order__customer"
    view_all_permission = "printing.view_all_printingorders"
    permission_classes = [DjangoModelPermissions, IsPrintingFactory]
    filter_backends = [
        DjangoFilterBackend,
        filters.SearchFilter,
        filters.OrderingFilter,
    ]
    filterset_class = PrintingJobFilterSet
    search_fields = ["product__name", "unit", "size", "description"]
    ordering_fields = ["id", "created_at", "updated_at", "quantity", "pieces"]
    ordering = ["-created_at"]

    def get_serializer_class(self):
        """根据动作选择序列化器"""
        if self.action == "list":
            return PrintingJobListSerializer
        elif self.action in ["create", "update", "partial_update"]:
            return PrintingJobCreateUpdateSerializer
        else:  # retrieve
            return PrintingJobDetailSerializer

    def get_queryset(self):
        """优化查询"""
        queryset = super().get_queryset()
        if self.action in ["list", "retrieve"]:
            from business.models import SalesOrderItem, SalesOrderStatusEnum

            queryset = queryset.select_related("printing_order", "printing_order__customer", "product")
            queryset = queryset.annotate(
                annotated_is_sales_order_bound=Exists(
                    SalesOrderItem.objects.filter(
                        printing_job_id=OuterRef("pk"),
                    ).exclude(sales_order__status=SalesOrderStatusEnum.CANCELLED)
                )
            )
            # 预取批量推进记录（避免 serializer 产生 N+1）
            queryset = queryset.prefetch_related(
                Prefetch(
                    "batch_advance_records",
                    queryset=models.PrintingJobBatchAdvanceRecord.objects.select_related(
                        "state", "created_by"
                    ).order_by("-created_at", "-id"),
                )
            )
        return queryset

    @staticmethod
    def _attach_saleitems_cache(jobs):
        if not jobs:
            return

        from shipment.services import get_active_sales_items_queryset

        job_ids = [job.id for job in jobs]
        saleitems = list(
            get_active_sales_items_queryset().select_related("shipment", "created_by")
            .filter(printing_job_id__in=job_ids)
            .order_by("id")
        )

        items_by_job_id = {job_id: [] for job_id in job_ids}
        for item in saleitems:
            items_by_job_id.setdefault(item.printing_job_id, []).append(item)

        for job in jobs:
            job._saleitems_cache = items_by_job_id.get(job.id, [])

    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())

        page = self.paginate_queryset(queryset)
        if page is not None:
            self._attach_saleitems_cache(page)
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        jobs = list(queryset)
        self._attach_saleitems_cache(jobs)
        serializer = self.get_serializer(jobs, many=True)
        return Response(serializer.data)

    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        self._attach_saleitems_cache([instance])
        serializer = self.get_serializer(instance)
        return Response(serializer.data)

    @action(detail=False, methods=["post"], url_path="mark-production-completed")
    def mark_production_completed(self, request):
        """
        显式标记印染任务已完成生产。

        请求体:
        {
            "printing_job_id": 123
        }
        """
        printing_job_id = request.data.get("printing_job_id")
        if not printing_job_id:
            return Response(
                {"detail": "printing_job_id 不能为空"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        user = request.user
        merchant = None
        if hasattr(user, "employee") and user.employee and user.employee.merchant:
            merchant = user.employee.merchant

        if merchant is None:
            return Response(
                {"detail": "当前用户未关联商户"},
                status=status.HTTP_403_FORBIDDEN,
            )

        try:
            job = models.PrintingJob.objects.get(pk=printing_job_id)
        except models.PrintingJob.DoesNotExist:
            return Response(
                {"detail": "PrintingJob 不存在"},
                status=status.HTTP_404_NOT_FOUND,
            )

        job_merchant = getattr(job, "merchant", None)
        if job_merchant is None and getattr(job, "printing_order", None):
            job_merchant = getattr(job.printing_order, "merchant", None)

        if job_merchant and job_merchant.id != merchant.id:
            return Response(
                {"detail": "您不能操作其他商户的 PrintingJob"},
                status=status.HTTP_403_FORBIDDEN,
            )

        from .services import PrintingJobService

        PrintingJobService.make_printing_job_production_completed(
            job,
            triggered_by=user,
        )
        return Response({"message": "OK"}, status=status.HTTP_200_OK)

    def destroy(self, request, *args, **kwargs):
        """禁用删除操作"""
        return Response(
            {"detail": "印染款式明细不支持删除操作"},
            status=status.HTTP_405_METHOD_NOT_ALLOWED,
        )

    @action(detail=True, methods=["post"], url_path="advance-to-next-state")
    def advance_to_next_state(self, request, pk=None):
        """
        推进到下一个状态

        将印染任务推进到下一个流程状态

        请求体:
        {
            "parameters": {
                "temperature": "25.5",
                "operator": "张三"
            }
        }

        参数可选，但如果下一个状态有必填参数，则必须提供

        注意：这是对 stateflow.BusinessObject.advance 接口的便捷包装，
        自动通过 printing_job 查找关联的 business_object 并调用状态流转服务。
        前端只需提供 printing_job 的 ID，无需关心 business_object_id。
        """
        job = self.get_object()

        if not job.business_object:
            return Response(
                {"detail": "该任务没有关联的流程实例"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        # 获取请求参数
        parameters = request.data.get("parameters", {})

        # 调用 stateflow 统一服务进行状态流转
        from stateflow import services as stateflow_services

        # 修复历史/异常数据：确保该 job 的流程实例正确绑定到 job（避免 BusinessObject.object_id/content_type 为空）
        stateflow_services.ensure_business_object_bound_to_instance(
            job.business_object,
            job,
            default_name=f"PrintingJob-{job.id}",
        )
        success, message, state_log = stateflow_services.advance_to_next_state(
            job.business_object, request.user, **parameters
        )

        if not success:
            return Response({"detail": message}, status=status.HTTP_400_BAD_REQUEST)

        # 更新 updated_at 以使缓存失效
        job.save(update_fields=["updated_at"])

        # 重新获取 job 以刷新状态
        job.refresh_from_db()
        serializer = PrintingJobDetailSerializer(job)

        return Response({"detail": message, "data": serializer.data})

    @action(detail=True, methods=["post"], url_path="step-back-one-state")
    def step_back_one_state(self, request, pk=None):
        """
        回退一步

        将印染任务回退到上一个流程状态

        注意：这是对 stateflow.BusinessObject.step_back 接口的便捷包装，
        自动通过 printing_job 查找关联的 business_object 并调用状态流转服务。
        前端只需提供 printing_job 的 ID，无需关心 business_object_id。
        """
        job = self.get_object()

        if not job.business_object:
            return Response(
                {"detail": "该任务没有关联的流程实例"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        # 调用 stateflow 统一服务进行状态回退
        from stateflow import services as stateflow_services

        success, message = stateflow_services.step_back_one_state(
            job.business_object, request.user
        )

        if not success:
            return Response({"detail": message}, status=status.HTTP_400_BAD_REQUEST)

        # 更新 updated_at 以使缓存失效
        job.save(update_fields=["updated_at"])

        # 重新获取 job 以刷新状态
        job.refresh_from_db()
        serializer = PrintingJobDetailSerializer(job)

        return Response({"detail": message, "data": serializer.data})

    @action(detail=True, methods=["get"], url_path="completed-states")
    def completed_states(self, request, pk=None):
        """
        查询已完成的流程列表

        查询参数:
        - include_cancelled: 是否包含已撤销的流程（true/false），默认 false
        """
        job = self.get_object()

        if not job.business_object:
            return Response({"count": 0, "results": []})

        # 获取查询参数
        include_cancelled = (
            request.query_params.get("include_cancelled", "false").lower() == "true"
        )

        # 查询已完成的状态
        state_logs = job.business_object.state_logs.select_related(
            "state", "completed_by"
        ).order_by("completed_at")

        if not include_cancelled:
            state_logs = state_logs.filter(is_cancelled=False)

        results = []
        for log in state_logs:
            results.append(
                {
                    "id": log.id,
                    "state_id": log.state.id,
                    "state_name": log.state.name,
                    "completed_at": log.completed_at,
                    "completed_by": log.completed_by.username
                    if log.completed_by
                    else None,
                    "completed_by_name": getattr(log.completed_by, "employee", None)
                    and log.completed_by.employee.name
                    or None,
                    "is_cancelled": log.is_cancelled,
                    "cancelled_at": log.cancelled_at,
                }
            )

        return Response({"count": len(results), "results": results})

    @action(detail=True, methods=["get"], url_path="timeline")
    def timeline(self, request, pk=None):
        """
        获取流程时间线

        返回该任务的完整流程时间线，包括未开始、进行中和已完成的状态
        不包含已撤销的流程记录
        """
        job = self.get_object()

        if not job.business_object or not job.printing_order.process:
            return Response({"count": 0, "results": []})

        from stateflow import services as stateflow_services

        timeline_data = stateflow_services.get_business_object_state_timeline(
            job.business_object
        )

        # 过滤掉已撤销的记录
        timeline_data = [item for item in timeline_data if not item.get("is_cancelled")]

        results = []
        for item in timeline_data:
            results.append(
                {
                    "state_id": item["state"].id,
                    "state_name": item["state"].name,
                    "state_description": item["state"].description,
                    "order": item["order"],
                    "status": item["status"],  # not_started, in_progress, completed
                    "completed_at": item["completed_at"],
                    "completed_by": item["completed_by"].username
                    if item["completed_by"]
                    else None,
                    "completed_by_name": getattr(item["completed_by"], "employee", None)
                    and item["completed_by"].employee.name
                    or None
                    if item["completed_by"]
                    else None,
                }
            )

        return Response({"count": len(results), "results": results})


class HasInvalidatePlateOrderPermission(BasePermission):
    """自定义权限类，检查用户是否有作废开版订单的权限"""

    message = "您没有权限作废开版订单"

    def has_permission(self, request, view):
        if not request.user.is_authenticated:
            return False
        return request.user.has_perm("printing.can_invalidate_plateorder")


class HasActivatePlateOrderPermission(BasePermission):
    """自定义权限类，检查用户是否有恢复开版订单的权限"""

    message = "您没有权限恢复开版订单"

    def has_permission(self, request, view):
        if not request.user.is_authenticated:
            return False
        return request.user.has_perm("printing.can_activate_plateorder")


class PlateOrderFilterSet(django_filters.FilterSet):
    """开版订单过滤器"""

    MAX_RANGE_DAYS = 10
    customer_name = django_filters.CharFilter(
        field_name="customer__name", lookup_expr="icontains"
    )
    customer_phone = django_filters.CharFilter(
        field_name="customer__mobile", lookup_expr="icontains"
    )
    image_name = django_filters.CharFilter(lookup_expr="icontains")
    salesperson = django_filters.NumberFilter()
    merchandiser = django_filters.NumberFilter()
    plate_type = django_filters.CharFilter(lookup_expr="icontains")
    urgency_level = django_filters.CharFilter(lookup_expr="icontains")
    is_invalid = django_filters.BooleanFilter()
    is_ordered = django_filters.BooleanFilter()
    is_mark_frame = django_filters.BooleanFilter()
    fabric = django_filters.CharFilter(lookup_expr="icontains")
    style_name = django_filters.CharFilter(lookup_expr="icontains")
    plate_date_from = django_filters.DateFilter(
        field_name="plate_date", lookup_expr="gte"
    )
    plate_date_to = django_filters.DateFilter(
        field_name="plate_date", method="filter_plate_date_to"
    )
    # plate_at_from / plate_at_to: 支持带时分秒的精确时间筛选（ISO 8601 格式）
    plate_at_from = IsoDateTimeFilter(field_name="plate_date", lookup_expr="gte")
    plate_at_to = IsoDateTimeFilter(field_name="plate_date", lookup_expr="lte")
    required_completion_date_from = IsoDateTimeFilter(
        field_name="required_completion_date", lookup_expr="gte"
    )
    required_completion_date_to = IsoDateTimeFilter(
        field_name="required_completion_date", lookup_expr="lte"
    )
    created_date_from = django_filters.DateFilter(
        field_name="created_at", lookup_expr="gte"
    )
    created_date_to = django_filters.DateFilter(
        field_name="created_at", method="filter_created_date_to"
    )

    class Meta:
        model = models.PlateOrder
        fields = [
            "customer",
            "salesperson",
            "merchandiser",
            "urgency_level",
            "is_invalid",
            "is_ordered",
        ]

    def filter_plate_date_to(self, queryset, name, value):
        """
        过滤 plate_date <= 指定日期的 23:59:59.999999
        使用 lt 次日 00:00:00 实现，确保包含整天
        """
        if value:
            from datetime import timedelta

            next_day = value + timedelta(days=1)
            return queryset.filter(plate_date__lt=next_day)
        return queryset

    def filter_created_date_to(self, queryset, name, value):
        """
        过滤 created_at <= 指定日期的 23:59:59.999999
        使用 lt 次日 00:00:00 实现，确保包含整天
        """
        if value:
            from datetime import timedelta

            next_day = value + timedelta(days=1)
            return queryset.filter(created_at__lt=next_day)
        return queryset

    @staticmethod
    def _to_local_date_from_datetime(raw_value):
        dt = parse_datetime(str(raw_value))
        if dt is None:
            raise DRFValidationError("日期时间格式无效")
        if timezone.is_aware(dt):
            dt = timezone.localtime(dt)
        return dt.date()

    @staticmethod
    def _to_date(raw_value):
        d = parse_date(str(raw_value))
        if d is None:
            raise DRFValidationError("日期格式无效")
        return d

    def _validate_range_days(self, from_key, to_key, mode, label):
        raw_from = self.data.get(from_key)
        raw_to = self.data.get(to_key)
        if not raw_from or not raw_to:
            return

        if mode == "datetime":
            from_date = self._to_local_date_from_datetime(raw_from)
            to_date = self._to_local_date_from_datetime(raw_to)
        else:
            from_date = self._to_date(raw_from)
            to_date = self._to_date(raw_to)

        days = (to_date - from_date).days + 1
        if days > self.MAX_RANGE_DAYS:
            raise DRFValidationError(f"{label}查询跨度不能超过{self.MAX_RANGE_DAYS}天")

    @property
    def qs(self):
        self._validate_range_days(
            "plate_date_from", "plate_date_to", mode="date", label="开版日期"
        )
        self._validate_range_days(
            "plate_at_from", "plate_at_to", mode="datetime", label="开版时间"
        )
        self._validate_range_days(
            "required_completion_date_from",
            "required_completion_date_to",
            mode="datetime",
            label="要求完成时间",
        )
        self._validate_range_days(
            "created_date_from", "created_date_to", mode="date", label="创建日期"
        )
        return super().qs


def _plate_order_last_modified(request, *args, **kwargs):
    """
    返回 PlateOrder 表中最新的 updated_at 时间。
    用于条件请求（304 Not Modified）判断。
    """
    latest = (
        models.PlateOrder.objects.order_by("-updated_at")
        .values_list("updated_at", flat=True)
        .first()
    )
    return latest


# @method_decorator(condition(last_modified_func=_plate_order_last_modified), name='list')
class PlateOrderViewSet(CustomerVisibilityFilterMixin, LimitedModelViewSet):
    """
    开版订单 ViewSet

    提供开版订单的增改查功能（不支持删除）

    list: 获取开版订单列表
    retrieve: 获取开版订单详情
    create: 创建开版订单
    image_name: 图片名称（模糊查询）
    update: 更新开版订单
    partial_update: 部分更新开版订单
    invalidate: 作废开版订单
    activate: 恢复开版订单
    advance_to_next_state: 推进到下一个状态（便捷包装）
    step_back_one_state: 回退一步（便捷包装）
    completed_states: 查询已完成的流程列表
    timeline: 获取流程时间线

    注意：状态流转接口是对 stateflow 模块的便捷包装
    - 前端只需提供 plate_order 的 ID
    - 系统会自动查找关联的 business_object 并进行状态流转
    - 也可以直接使用 stateflow 的原始接口（需要 business_object_id）

    查询参数:
    - customer: 客户ID
    - customer_name: 客户名称（模糊查询）
    - customer_phone: 客户电话（模糊查询）
    - salesperson: 业务员ID
    - merchandiser: 跟单员ID
    - plate_type: 版型（模糊查询）
    - urgency_level: 紧急程度（1-5）
    - is_invalid: 是否作废（true/false）
    - is_ordered: 是否已下单（true/false）
    - is_mark_frame: 是否打样架（true/false）
    - fabric: 面料（模糊查询）
    - style_name: 款式名称（模糊查询）
    - plate_date_from: 开版日期起始（仅日期）
    - plate_date_to: 开版日期结束（仅日期，包含整天）
    - plate_at_from: 开版时间起始（ISO 8601 格式，含时分秒，如 2024-01-01T08:00:00）
    - plate_at_to: 开版时间结束（ISO 8601 格式，含时分秒）
    - required_completion_date_from: 要求完成时间起始（含时分秒）
    - required_completion_date_to: 要求完成时间结束
    - created_date_from: 创建日期起始
    - created_date_to: 创建日期结束
    - search: 全文搜索（设计编号、款式名称、客户名称、面料）
    - ordering: 排序字段

    权限控制:
    - 默认按客户可见性过滤（员工只能看到自己负责的客户的订单）
    - 拥有 printing.view_all_plateorders 权限可突破此限制
    """

    queryset = models.PlateOrder.objects.all()
    customer_field = "customer"
    view_all_permission = "printing.view_all_plateorders"
    permission_classes = [DjangoModelPermissions]
    parser_classes = [MultiPartParser, FormParser, JSONParser]  # 支持文件上传
    filter_backends = [
        DjangoFilterBackend,
        filters.SearchFilter,
        filters.OrderingFilter,
    ]
    filterset_class = PlateOrderFilterSet
    # search_fields 说明（模糊搜索支持的字段）：
    # - design_code: 设计编号
    # - design_code_normalized: 设计编号兜底（为空时使用主键 ID，通过 get_queryset 注解）
    # - style_name: 款号名称
    # - customer__name: 客户名称（跨表）
    # - fabric: 布料
    # - plate_notes: 打版注意事项
    # - reprint_reason: 复版原因
    # - image_name: 图片名称
    # - default_address: 默认地址
    # 待添加：area, customer_feedback, fabric_source, width 等
    search_fields = [
        "design_code",
        "design_code_normalized",
        "style_name",
        "customer__name",
        "fabric",
        "plate_notes",
        "reprint_reason",
        "image_name",
        "default_address",
    ]
    ordering_fields = [
        "id",
        "created_at",
        "updated_at",
        "plate_date",
        "required_completion_date",
        "completion_date",
        "urgency_level",
        "is_ordered",
        "is_invalid",
    ]
    ordering = ["-created_at"]

    def get_serializer_class(self):
        """根据动作选择序列化器"""
        from .serializers import (
            PlateOrderKanbanListSerializer,
            PlateOrderListSerializer,
            PlateOrderDetailSerializer,
            PlateOrderCreateUpdateSerializer,
        )

        if self.action == "list":
            return PlateOrderKanbanListSerializer
        elif self.action in ["create", "update", "partial_update"]:
            return PlateOrderCreateUpdateSerializer
        else:  # retrieve
            return PlateOrderDetailSerializer

    def get_queryset(self):
        """优化查询"""
        queryset = super().get_queryset()

        # 默认不返回作废订单（仅 list）；如显式传入 is_invalid 参数，则交由 FilterSet 处理
        # 注意：不能影响 activate/invalidate 等 action，否则会因 get_object() 被过滤而 404。
        if (
            self.action == "list"
            and self.request
            and "is_invalid" not in self.request.query_params
        ):
            queryset = queryset.filter(is_invalid=False)

        if self.action in ["list", "retrieve"]:
            queryset = queryset.select_related(
                "customer",
                "salesperson",
                "merchandiser",
                "business_object",
                "created_by__employee",
            )

        # 预取流程名称（process 是 IntegerField 非外键，用 Subquery 避免 N+1）
        from stateflow.models import Process

        queryset = queryset.annotate(
            design_code_normalized=Coalesce(
                "design_code", Cast("id", output_field=CharField())
            ),
            annotated_process_name=Subquery(
                Process.objects.filter(id=OuterRef("process")).values("name")[:1],
                output_field=CharField(),
            ),
        )
        return queryset

    def destroy(self, request, *args, **kwargs):
        """禁用删除操作"""
        return Response(
            {"detail": "开版订单不支持删除操作，请使用作废功能"},
            status=status.HTTP_405_METHOD_NOT_ALLOWED,
        )

    def perform_create(self, serializer):
        """
        创建时自动绑定创建人（created_by）和商户（merchant），不允许前端传参控制。
        """
        user = self.request.user
        merchant = None
        if hasattr(user, "employee") and user.employee and user.employee.merchant:
            merchant = user.employee.merchant
        serializer.save(created_by=user, merchant=merchant)

    @action(detail=True, methods=["post"])
    def invalidate(self, request, pk=None):
        """
        作废开版订单

        需要权限: printing.can_invalidate_plateorder
        """
        # 检查权限
        if not request.user.has_perm("printing.can_invalidate_plateorder"):
            return Response(
                {"detail": "您没有权限作废开版订单"}, status=status.HTTP_403_FORBIDDEN
            )

        plate_order = self.get_object()

        if plate_order.is_invalid:
            return Response(
                {"detail": "该开版订单已经作废"}, status=status.HTTP_400_BAD_REQUEST
            )

        plate_order.is_invalid = True
        plate_order.save()

        from .serializers import PlateOrderDetailSerializer

        serializer = PlateOrderDetailSerializer(
            plate_order, context={"request": request}
        )
        return Response({"detail": "开版订单已作废", "data": serializer.data})

    @action(detail=True, methods=["post"])
    def activate(self, request, pk=None):
        """
        恢复开版订单

        需要权限: printing.can_activate_plateorder
        """
        # 检查权限
        if not request.user.has_perm("printing.can_activate_plateorder"):
            return Response(
                {"detail": "您没有权限恢复开版订单"}, status=status.HTTP_403_FORBIDDEN
            )

        plate_order = self.get_object()

        if not plate_order.is_invalid:
            return Response(
                {"detail": "该开版订单未作废，无需恢复"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        plate_order.is_invalid = False
        plate_order.save()

        from .serializers import PlateOrderDetailSerializer

        serializer = PlateOrderDetailSerializer(
            plate_order, context={"request": request}
        )
        return Response({"detail": "开版订单已恢复", "data": serializer.data})

    @action(detail=True, methods=["post"], url_path="advance-to-next-state")
    def advance_to_next_state(self, request, pk=None):
        """
        推进到下一个状态

        将开版订单推进到下一个流程状态

        请求体:
        {
            "parameters": {
                "inspector": "李四",
                "quality_score": "95"
            }
        }

        参数可选，但如果下一个状态有必填参数，则必须提供

        注意：这是对 stateflow.BusinessObject.advance 接口的便捷包装，
        自动通过 plate_order 查找关联的 business_object 并调用状态流转服务。
        """
        plate_order = self.get_object()

        if not plate_order.business_object:
            return Response(
                {"detail": "该开版订单没有关联的流程实例"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        # 获取请求参数
        parameters = request.data.get("parameters", {})

        # 调用 stateflow 统一服务进行状态流转
        from stateflow import services as stateflow_services

        # 修复历史/异常数据：确保该 plate_order 的流程实例正确绑定到 plate_order（避免 BusinessObject.object_id/content_type 为空）
        stateflow_services.ensure_business_object_bound_to_instance(
            plate_order.business_object,
            plate_order,
            default_name=f"PlateOrder-{plate_order.id}",
        )
        success, message, state_log = stateflow_services.advance_to_next_state(
            plate_order.business_object, request.user, **parameters
        )

        if not success:
            return Response({"detail": message}, status=status.HTTP_400_BAD_REQUEST)

        # 更新 updated_at 以使缓存失效
        plate_order.save(update_fields=["updated_at"])

        # 重新获取 plate_order 以刷新状态
        plate_order.refresh_from_db()
        from .serializers import PlateOrderDetailSerializer

        serializer = PlateOrderDetailSerializer(
            plate_order, context={"request": request}
        )

        return Response({"detail": message, "data": serializer.data})

    @action(detail=True, methods=["post"], url_path="step-back-one-state")
    def step_back_one_state(self, request, pk=None):
        """
        回退一步

        将开版订单回退到上一个流程状态

        注意：这是对 stateflow.BusinessObject.step_back 接口的便捷包装，
        自动通过 plate_order 查找关联的 business_object 并调用状态流转服务。
        """
        plate_order = self.get_object()

        if not plate_order.business_object:
            return Response(
                {"detail": "该开版订单没有关联的流程实例"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        # 调用 stateflow 统一服务进行状态回退
        from stateflow import services as stateflow_services

        success, message = stateflow_services.step_back_one_state(
            plate_order.business_object, request.user
        )

        if not success:
            return Response({"detail": message}, status=status.HTTP_400_BAD_REQUEST)

        # 更新 updated_at 以使缓存失效
        plate_order.save(update_fields=["updated_at"])

        # 重新获取 plate_order 以刷新状态
        plate_order.refresh_from_db()
        from .serializers import PlateOrderDetailSerializer

        serializer = PlateOrderDetailSerializer(
            plate_order, context={"request": request}
        )

        return Response({"detail": message, "data": serializer.data})

    @action(detail=True, methods=["get"], url_path="completed-states")
    def completed_states(self, request, pk=None):
        """
        查询已完成的流程列表

        查询参数:
        - include_cancelled: 是否包含已撤销的流程（true/false），默认 false
        """
        plate_order = self.get_object()

        if not plate_order.business_object:
            return Response({"count": 0, "results": []})

        # 获取查询参数
        include_cancelled = (
            request.query_params.get("include_cancelled", "false").lower() == "true"
        )

        # 查询已完成的状态
        state_logs = plate_order.business_object.state_logs.select_related(
            "state", "completed_by"
        ).order_by("completed_at")

        if not include_cancelled:
            state_logs = state_logs.filter(is_cancelled=False)

        results = []
        for log in state_logs:
            results.append(
                {
                    "id": log.id,
                    "state_id": log.state.id,
                    "state_name": log.state.name,
                    "completed_at": log.completed_at,
                    "completed_by": log.completed_by.username
                    if log.completed_by
                    else None,
                    "completed_by_name": getattr(log.completed_by, "employee", None)
                    and log.completed_by.employee.name
                    or None,
                    "is_cancelled": log.is_cancelled,
                    "cancelled_at": log.cancelled_at,
                }
            )

        return Response({"count": len(results), "results": results})

    @action(detail=True, methods=["get"], url_path="timeline")
    def timeline(self, request, pk=None):
        """
        获取流程时间线

        返回该开版订单的完整流程时间线，包括未开始、进行中和已完成的状态
        不包含已撤销的流程记录
        """
        plate_order = self.get_object()

        if not plate_order.business_object:
            return Response({"count": 0, "results": []})

        from stateflow import services as stateflow_services

        # 获取流程
        from stateflow.models import Process

        try:
            process = Process.objects.get(id=plate_order.process)
        except Process.DoesNotExist:
            return Response({"count": 0, "results": []})

        timeline_data = stateflow_services.get_business_object_state_timeline(
            plate_order.business_object
        )

        # 过滤掉已撤销的记录
        timeline_data = [item for item in timeline_data if not item.get("is_cancelled")]

        results = []
        for item in timeline_data:
            results.append(
                {
                    "state_id": item["state"].id,
                    "state_name": item["state"].name,
                    "state_description": item["state"].description,
                    "order": item["order"],
                    "status": item["status"],  # not_started, in_progress, completed
                    "completed_at": item["completed_at"],
                    "completed_by": item["completed_by"].username
                    if item["completed_by"]
                    else None,
                    "completed_by_name": getattr(item["completed_by"], "employee", None)
                    and item["completed_by"].employee.name
                    or None
                    if item["completed_by"]
                    else None,
                }
            )

        return Response({"count": len(results), "results": results})
