from datetime import datetime, time, timedelta

from django.contrib.contenttypes.models import ContentType
from django.core.cache import cache
from django.db import IntegrityError
from django.db.models import Count, Exists, OuterRef, Prefetch, Q
from django.db.models import ProtectedError
from django.shortcuts import get_object_or_404
from django.utils import timezone
from django.utils.dateparse import parse_date, parse_datetime
from drf_spectacular.utils import extend_schema
from rest_framework import permissions, serializers, status
from rest_framework.response import Response
from rest_framework.views import APIView

from flower.viewsets import LimitedLimitOffsetPagination
from mission import models as mission_models
from mission import services as mission_services


STATUS_FIELDS = {"is_urgent", "is_completed", "is_cancelled", "cancelled_by", "cancelled_at", "rejected_by", "rejected_at"}
MISSION_BY_PRINTING_ORDER_CACHE_TIMEOUT = 180


def _get_employee(request):
    employee = getattr(request.user, "employee", None)
    if employee is None:
        raise serializers.ValidationError("当前用户未关联员工")
    return employee


def _employee_payload(employee):
    if employee is None:
        return None
    return {
        "id": employee.id,
        "name": employee.name,
        "merchant_id": employee.merchant_id,
    }


class MissionWriteSerializer(serializers.Serializer):
    description = serializers.CharField(required=False, allow_blank=False)
    category = serializers.IntegerField(required=False, min_value=1)
    content_type = serializers.IntegerField(required=False, allow_null=True, min_value=1)
    content_id = serializers.IntegerField(required=False, allow_null=True, min_value=1)
    extra = serializers.JSONField(required=False, allow_null=True)
    notify_if_unreplied = serializers.BooleanField(required=False)
    unreplied_notify_interval_minutes = serializers.IntegerField(required=False, allow_null=True, min_value=1)
    unreplied_notify_max_count = serializers.IntegerField(required=False, min_value=1)
    participant_ids = serializers.ListField(
        child=serializers.IntegerField(min_value=1),
        required=False,
        allow_empty=True,
    )
    employee_type_ids = serializers.ListField(
        child=serializers.IntegerField(min_value=1),
        required=False,
        allow_empty=True,
    )

    def __init__(self, *args, **kwargs):
        self.is_create = kwargs.pop("is_create", False)
        super().__init__(*args, **kwargs)
        if self.is_create:
            self.fields["description"].required = True

    def validate_category(self, value):
        employee = self.context.get("employee")
        if employee is None:
            return value
        try:
            return mission_models.MissionCategory.objects.get(id=value, merchant=employee.merchant)
        except mission_models.MissionCategory.DoesNotExist as exc:
            raise serializers.ValidationError("任务分类不存在") from exc

    def validate(self, attrs):
        forbidden_fields = STATUS_FIELDS.intersection(self.initial_data.keys())
        if forbidden_fields:
            raise serializers.ValidationError({field: "状态字段不能通过普通 CRUD 接口修改" for field in sorted(forbidden_fields)})

        content_type_provided = "content_type" in self.initial_data
        content_id_provided = "content_id" in self.initial_data
        if content_type_provided != content_id_provided:
            raise serializers.ValidationError("content_type 与 content_id 必须同时提供或同时省略")

        content_type_id = attrs.get("content_type")
        if content_type_id is not None:
            try:
                attrs["content_type"] = ContentType.objects.get(id=content_type_id)
            except ContentType.DoesNotExist as exc:
                raise serializers.ValidationError({"content_type": "ContentType 不存在"}) from exc

        instance = self.context.get("instance")
        final_notify_if_unreplied = attrs.get(
            "notify_if_unreplied",
            getattr(instance, "notify_if_unreplied", False),
        )
        final_interval = attrs.get(
            "unreplied_notify_interval_minutes",
            getattr(instance, "unreplied_notify_interval_minutes", None),
        )
        if final_notify_if_unreplied and final_interval is None:
            raise serializers.ValidationError(
                {"unreplied_notify_interval_minutes": "开启未回复提醒时必须设置提醒间隔"}
            )
        return attrs


class MissionReplyCreateSerializer(serializers.Serializer):
    content = serializers.CharField(allow_blank=False)
    ends_task = serializers.BooleanField(required=False, default=False)
    extra = serializers.JSONField(required=False, allow_null=True)


class MissionUrgentSerializer(serializers.Serializer):
    is_urgent = serializers.BooleanField()


class MissionCategoryWriteSerializer(serializers.Serializer):
    name = serializers.CharField(allow_blank=False, max_length=50)
    speech_enabled = serializers.BooleanField(required=False)
    payload_processor = serializers.ChoiceField(
        choices=mission_models.MissionPayloadProcessorEnum.choices,
        required=False,
        allow_blank=True,
    )

    def validate_name(self, value):
        value = value.strip()
        if not value:
            raise serializers.ValidationError("分类名称不能为空")
        employee = self.context["employee"]
        instance = self.context.get("instance")
        queryset = mission_models.MissionCategory.objects.filter(
            merchant=employee.merchant,
            name=value,
        )
        if instance is not None:
            queryset = queryset.exclude(id=instance.id)
        if queryset.exists():
            raise serializers.ValidationError("分类名称已存在")
        return value


class MissionSerializer(serializers.ModelSerializer):
    creator = serializers.SerializerMethodField()
    cancelled_by = serializers.SerializerMethodField()
    participants = serializers.SerializerMethodField()
    content_type_label = serializers.SerializerMethodField()
    category = serializers.IntegerField(source="category_id", read_only=True)
    category_name = serializers.CharField(source="category.name", read_only=True)
    has_ending_reply = serializers.SerializerMethodField()
    can_reply = serializers.SerializerMethodField()

    class Meta:
        model = mission_models.Mission
        fields = [
            "id",
            "merchant",
            "description",
            "category",
            "category_name",
            "is_urgent",
            "is_completed",
            "is_cancelled",
            "notify_if_unreplied",
            "unreplied_notify_interval_minutes",
            "unreplied_notify_max_count",
            "unreplied_notify_sent_count",
            "unreplied_last_notified_at",
            "cancelled_at",
            "creator",
            "cancelled_by",
            "participants",
            "content_type",
            "content_type_label",
            "content_id",
            "extra",
            "has_ending_reply",
            "can_reply",
            "created_at",
            "updated_at",
        ]
        read_only_fields = fields

    def get_creator(self, obj):
        return _employee_payload(obj.creator)

    def get_cancelled_by(self, obj):
        return _employee_payload(obj.cancelled_by)

    def get_participants(self, obj):
        prefetched_participants = getattr(obj, "_prefetched_objects_cache", {}).get("participants")
        if prefetched_participants is not None:
            return [_employee_payload(participant.employee) for participant in prefetched_participants]
        return [
            _employee_payload(participant.employee)
            for participant in obj.participants.select_related("employee", "employee__merchant")
        ]

    def get_content_type_label(self, obj):
        if obj.content_type_id is None:
            return None
        return f"{obj.content_type.app_label}.{obj.content_type.model}"

    def get_has_ending_reply(self, obj):
        annotated_value = getattr(obj, "has_ending_reply_value", None)
        if annotated_value is not None:
            return annotated_value
        return obj.has_ending_reply

    def get_can_reply(self, obj):
        return not obj.is_cancelled and not self.get_has_ending_reply(obj)


class MissionLiteSerializer(serializers.ModelSerializer):
    creator = serializers.SerializerMethodField()
    cancelled_by = serializers.SerializerMethodField()
    content_type_label = serializers.SerializerMethodField()
    category = serializers.IntegerField(source="category_id", read_only=True)
    category_name = serializers.CharField(source="category.name", read_only=True)

    class Meta:
        model = mission_models.Mission
        fields = [
            "id",
            "merchant",
            "description",
            "category",
            "category_name",
            "is_urgent",
            "is_completed",
            "is_cancelled",
            "notify_if_unreplied",
            "unreplied_notify_interval_minutes",
            "unreplied_notify_max_count",
            "unreplied_notify_sent_count",
            "unreplied_last_notified_at",
            "cancelled_at",
            "creator",
            "cancelled_by",
            "content_type",
            "content_type_label",
            "content_id",
            "extra",
            "created_at",
            "updated_at",
        ]
        read_only_fields = fields

    def get_creator(self, obj):
        return _employee_payload(obj.creator)

    def get_cancelled_by(self, obj):
        return _employee_payload(obj.cancelled_by)

    def get_content_type_label(self, obj):
        if obj.content_type_id is None:
            return None
        return f"{obj.content_type.app_label}.{obj.content_type.model}"


class MissionCategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = mission_models.MissionCategory
        fields = [
            "id",
            "merchant",
            "name",
            "payload_processor",
            "speech_enabled",
            "created_at",
            "updated_at",
        ]
        read_only_fields = fields


class MissionReplySerializer(serializers.ModelSerializer):
    responder = serializers.SerializerMethodField()
    rejected_by = serializers.SerializerMethodField()

    class Meta:
        model = mission_models.MissionReply
        fields = [
            "id",
            "mission",
            "merchant",
            "responder",
            "content",
            "extra",
            "replied_at",
            "ends_task",
            "is_rejected",
            "rejected_by",
            "rejected_at",
            "created_at",
            "updated_at",
        ]
        read_only_fields = fields

    def get_responder(self, obj):
        return _employee_payload(obj.responder)

    def get_rejected_by(self, obj):
        return _employee_payload(obj.rejected_by)


class MissionWithRepliesSerializer(MissionSerializer):
    replies = serializers.SerializerMethodField()

    class Meta(MissionSerializer.Meta):
        fields = [*MissionSerializer.Meta.fields, "replies"]
        read_only_fields = fields

    def get_replies(self, obj):
        replies = getattr(obj, "prefetched_replies", None)
        if replies is None:
            replies = (
                obj.replies.select_related(
                    "responder",
                    "responder__merchant",
                    "rejected_by",
                    "rejected_by__merchant",
                )
                .order_by("replied_at", "id")
            )
        return MissionReplySerializer(replies, many=True).data


def _parse_int_list_query_param(request, param_name: str) -> list[int] | None:
    raw_values = request.query_params.getlist(param_name)
    if not raw_values:
        single_value = request.query_params.get(param_name)
        if single_value is None:
            return None
        raw_values = [single_value]

    tokens = []
    for raw_value in raw_values:
        if raw_value is None:
            continue
        parts = [part.strip() for part in str(raw_value).split(",")]
        tokens.extend(part for part in parts if part)

    if not tokens:
        return None

    try:
        values = [int(token) for token in tokens]
    except (TypeError, ValueError) as exc:
        raise serializers.ValidationError({param_name: "必须是整数 ID 列表"}) from exc

    if any(value <= 0 for value in values):
        raise serializers.ValidationError({param_name: "必须是正整数 ID 列表"})
    return list(dict.fromkeys(values))


def _parse_bool_query_param(request, param_name: str, default: bool) -> bool:
    raw_value = request.query_params.get(param_name)
    if raw_value is None:
        return default

    value = str(raw_value).strip().lower()
    if value in {"1", "true", "yes", "y", "on"}:
        return True
    if value in {"0", "false", "no", "n", "off"}:
        return False
    raise serializers.ValidationError({param_name: "必须是布尔值"})


def _parse_date_or_datetime_query_param(value: str | None) -> tuple[datetime | None, bool]:
    raw_value = (value or "").strip()
    if not raw_value:
        return None, False

    tz = timezone.get_current_timezone()
    date_value = parse_date(raw_value)
    if date_value is not None:
        return timezone.make_aware(datetime.combine(date_value, time.min), tz), False

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

    return None, False


def _mission_queryset_for_employee(employee, *, include_details: bool = True):
    has_ending_reply_subquery = mission_models.MissionReply.objects.filter(
        mission_id=OuterRef("pk"),
        ends_task=True,
        is_rejected=False,
    )
    queryset = (
        mission_models.Mission.objects.filter(merchant=employee.merchant)
        .annotate(has_ending_reply_value=Exists(has_ending_reply_subquery))
        .select_related(
            "merchant",
            "category",
            "creator",
            "creator__merchant",
            "cancelled_by",
            "cancelled_by__merchant",
            "content_type",
        )
        .order_by("-created_at", "-id")
    )
    if include_details:
        queryset = queryset.prefetch_related("participants__employee", "participants__employee__merchant")
    return queryset


def _mission_by_printing_order_cache_key(*, merchant_id: int, printing_order_id: int, category_ids: list[int] | None,
                                         include_details: bool) -> str:
    category_key = "all" if category_ids is None else ",".join(str(category_id) for category_id in category_ids)
    detail_key = "detail" if include_details else "lite"
    return f"mission:by-printing-order:{merchant_id}:{printing_order_id}:{category_key}:{detail_key}"


def _mission_category_queryset_for_employee(employee):
    return mission_models.MissionCategory.objects.filter(merchant=employee.merchant).order_by("id")


class MissionCategoryListCreateView(APIView):
    permission_classes = [permissions.IsAuthenticated]

    def get(self, request):
        employee = _get_employee(request)
        queryset = _mission_category_queryset_for_employee(employee)
        return Response(MissionCategorySerializer(queryset, many=True).data)

    @extend_schema(request=MissionWriteSerializer, responses={201: MissionSerializer})
    def post(self, request):
        employee = _get_employee(request)
        serializer = MissionCategoryWriteSerializer(data=request.data, context={"employee": employee})
        serializer.is_valid(raise_exception=True)
        try:
            category = mission_models.MissionCategory.objects.create(
                merchant=employee.merchant,
                name=serializer.validated_data["name"],
                payload_processor=serializer.validated_data.get("payload_processor", ""),
                speech_enabled=serializer.validated_data.get("speech_enabled", False),
            )
        except IntegrityError:
            return Response({"name": ["分类名称已存在"]}, status=status.HTTP_400_BAD_REQUEST)
        return Response(MissionCategorySerializer(category).data, status=status.HTTP_201_CREATED)


class MissionCategoryDetailView(APIView):
    permission_classes = [permissions.IsAuthenticated]

    def get_object(self, request, category_id):
        employee = _get_employee(request)
        return get_object_or_404(_mission_category_queryset_for_employee(employee), id=category_id)

    def get(self, request, category_id):
        category = self.get_object(request, category_id)
        return Response(MissionCategorySerializer(category).data)

    def patch(self, request, category_id):
        employee = _get_employee(request)
        category = self.get_object(request, category_id)
        serializer = MissionCategoryWriteSerializer(
            data=request.data,
            partial=True,
            context={"employee": employee, "instance": category},
        )
        serializer.is_valid(raise_exception=True)
        update_fields = []
        if "name" in serializer.validated_data:
            category.name = serializer.validated_data["name"]
            update_fields.append("name")
        if "payload_processor" in serializer.validated_data:
            category.payload_processor = serializer.validated_data["payload_processor"]
            update_fields.append("payload_processor")
        if "speech_enabled" in serializer.validated_data:
            category.speech_enabled = serializer.validated_data["speech_enabled"]
            update_fields.append("speech_enabled")
        if update_fields:
            try:
                category.save(update_fields=[*update_fields, "updated_at"])
            except IntegrityError:
                return Response({"name": ["分类名称已存在"]}, status=status.HTTP_400_BAD_REQUEST)
        return Response(MissionCategorySerializer(category).data)

    def delete(self, request, category_id):
        category = self.get_object(request, category_id)
        try:
            category.delete()
        except ProtectedError:
            return Response({"detail": "任务分类已被使用，不能删除"}, status=status.HTTP_400_BAD_REQUEST)
        return Response(status=status.HTTP_204_NO_CONTENT)


class MissionListCreateView(APIView):
    permission_classes = [permissions.IsAuthenticated]
    pagination_class = LimitedLimitOffsetPagination

    def get(self, request):
        employee = _get_employee(request)
        queryset = _mission_queryset_for_employee(employee)

        if request.query_params.get("category"):
            queryset = queryset.filter(category_id=request.query_params["category"])
        for field in ["is_urgent", "is_completed", "is_cancelled"]:
            value = request.query_params.get(field)
            if value is not None:
                queryset = queryset.filter(**{field: value.lower() in ["1", "true", "yes"]})
        if request.query_params.get("content_type"):
            queryset = queryset.filter(content_type_id=request.query_params["content_type"])
        if request.query_params.get("content_id"):
            queryset = queryset.filter(content_id=request.query_params["content_id"])

        paginator = self.pagination_class()
        page = paginator.paginate_queryset(queryset, request, view=self)
        serializer = MissionSerializer(page, many=True)
        return paginator.get_paginated_response(serializer.data)

    def post(self, request):
        employee = _get_employee(request)
        serializer = MissionWriteSerializer(data=request.data, is_create=True, context={"employee": employee})
        serializer.is_valid(raise_exception=True)
        data = serializer.validated_data
        try:
            mission = mission_services.create_mission(
                creator=employee,
                description=data["description"],
                category=data.get("category"),
                content_type=data.get("content_type"),
                content_id=data.get("content_id"),
                extra=data.get("extra"),
                participant_ids=data.get("participant_ids"),
                employee_type_ids=data.get("employee_type_ids"),
                notify_if_unreplied=data.get("notify_if_unreplied", False),
                unreplied_notify_interval_minutes=data.get("unreplied_notify_interval_minutes"),
                unreplied_notify_max_count=data.get("unreplied_notify_max_count", 5),
            )
        except ValueError as exc:
            return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
        return Response(MissionSerializer(mission).data, status=status.HTTP_201_CREATED)


class MissionMyView(APIView):
    permission_classes = [permissions.IsAuthenticated]
    pagination_class = LimitedLimitOffsetPagination

    def get(self, request):
        employee = _get_employee(request)
        queryset = (
            _mission_queryset_for_employee(employee)
            .filter(Q(creator=employee) | Q(participants__employee=employee))
            .distinct()
        )

        if _parse_bool_query_param(request, "created_by_me", default=False):
            queryset = queryset.filter(creator=employee)

        for field in ["is_completed", "is_cancelled", "is_urgent"]:
            if request.query_params.get(field) is not None:
                queryset = queryset.filter(**{field: _parse_bool_query_param(request, field, default=False)})

        created_at_from = request.query_params.get("created_at_from")
        if created_at_from is not None:
            dt, _has_time = _parse_date_or_datetime_query_param(created_at_from)
            if dt is None:
                raise serializers.ValidationError({"created_at_from": "必须是日期或日期时间"})
            queryset = queryset.filter(created_at__gte=dt)

        created_at_to = request.query_params.get("created_at_to")
        if created_at_to is not None:
            dt, has_time = _parse_date_or_datetime_query_param(created_at_to)
            if dt is None:
                raise serializers.ValidationError({"created_at_to": "必须是日期或日期时间"})
            if has_time:
                queryset = queryset.filter(created_at__lte=dt)
            else:
                queryset = queryset.filter(created_at__lt=dt + timedelta(days=1))

        paginator = self.pagination_class()
        page = paginator.paginate_queryset(queryset, request, view=self)
        serializer = MissionSerializer(page, many=True)
        return paginator.get_paginated_response(serializer.data)


class MissionMyStatusCountsView(APIView):
    permission_classes = [permissions.IsAuthenticated]

    def get(self, request):
        employee = _get_employee(request)
        queryset = mission_models.Mission.objects.filter(merchant=employee.merchant).filter(
            Q(creator=employee) | Q(participants__employee=employee)
        )
        counts = queryset.aggregate(
            total=Count("id", distinct=True),
            open=Count("id", filter=Q(is_completed=False, is_cancelled=False), distinct=True),
            completed=Count("id", filter=Q(is_completed=True, is_cancelled=False), distinct=True),
            cancelled=Count("id", filter=Q(is_cancelled=True), distinct=True),
        )
        return Response(counts)


class MissionByPrintingOrderView(APIView):
    permission_classes = [permissions.IsAuthenticated]

    def get(self, request, printing_order_id):
        employee = _get_employee(request)

        from printing.models import PrintingOrder

        printing_order = get_object_or_404(
            PrintingOrder.objects.filter(merchant=employee.merchant),
            id=printing_order_id,
        )
        category_ids = _parse_int_list_query_param(request, "category_ids")
        include_details = _parse_bool_query_param(request, "include_details", default=True)
        cache_key = _mission_by_printing_order_cache_key(
            merchant_id=employee.merchant_id,
            printing_order_id=printing_order.id,
            category_ids=category_ids,
            include_details=include_details,
        )
        cached_data = cache.get(cache_key)
        if cached_data is not None:
            return Response(cached_data)

        printing_order_content_type = ContentType.objects.get_for_model(PrintingOrder)

        queryset = _mission_queryset_for_employee(employee, include_details=include_details).filter(
            content_type=printing_order_content_type,
            content_id=printing_order.id,
        )
        if category_ids is not None:
            queryset = queryset.filter(category_id__in=category_ids)
        if include_details:
            replies_queryset = mission_models.MissionReply.objects.select_related(
                "responder",
                "responder__merchant",
                "rejected_by",
                "rejected_by__merchant",
            ).order_by("replied_at", "id")
            queryset = queryset.prefetch_related(
                Prefetch("replies", queryset=replies_queryset, to_attr="prefetched_replies")
            )

        serializer_class = MissionWithRepliesSerializer if include_details else MissionLiteSerializer
        data = serializer_class(queryset, many=True).data
        cache.set(cache_key, data, timeout=MISSION_BY_PRINTING_ORDER_CACHE_TIMEOUT)

        return Response(data)


class MissionDetailView(APIView):
    permission_classes = [permissions.IsAuthenticated]

    def get_object(self, request, mission_id):
        employee = _get_employee(request)
        return get_object_or_404(_mission_queryset_for_employee(employee), id=mission_id)

    def get(self, request, mission_id):
        mission = self.get_object(request, mission_id)
        return Response(MissionSerializer(mission).data)

    @extend_schema(request=MissionWriteSerializer, responses=MissionSerializer)
    def patch(self, request, mission_id):
        employee = _get_employee(request)
        mission = self.get_object(request, mission_id)
        serializer = MissionWriteSerializer(
            data=request.data,
            partial=True,
            context={"employee": employee, "instance": mission},
        )
        serializer.is_valid(raise_exception=True)
        data = serializer.validated_data
        try:
            mission = mission_services.update_mission(
                mission=mission,
                updated_by=employee,
                description=data.get("description"),
                category=data.get("category"),
                content_type=data.get("content_type"),
                content_id=data.get("content_id"),
                extra=(data["extra"] if "extra" in data else mission_services.UNSET),
                update_content_object=("content_type" in request.data or "content_id" in request.data),
                participant_ids=data.get("participant_ids"),
                employee_type_ids=data.get("employee_type_ids"),
                notify_if_unreplied=(
                    data["notify_if_unreplied"]
                    if "notify_if_unreplied" in data
                    else mission_services.UNSET
                ),
                unreplied_notify_interval_minutes=(
                    data["unreplied_notify_interval_minutes"]
                    if "unreplied_notify_interval_minutes" in data
                    else mission_services.UNSET
                ),
                unreplied_notify_max_count=(
                    data["unreplied_notify_max_count"]
                    if "unreplied_notify_max_count" in data
                    else mission_services.UNSET
                ),
            )
        except ValueError as exc:
            return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
        return Response(MissionSerializer(mission).data)

    def delete(self, request, mission_id):
        return Response({"detail": "Mission 删除接口未提供，请使用 cancel 接口取消任务"}, status=status.HTTP_405_METHOD_NOT_ALLOWED)


class MissionReplyListCreateView(APIView):
    permission_classes = [permissions.IsAuthenticated]

    def get(self, request, mission_id):
        employee = _get_employee(request)
        mission = get_object_or_404(_mission_queryset_for_employee(employee), id=mission_id)
        queryset = (
            mission_models.MissionReply.objects.filter(mission=mission)
            .select_related("responder", "responder__merchant", "rejected_by", "rejected_by__merchant")
            .order_by("replied_at", "id")
        )
        for field in ["ends_task", "is_rejected"]:
            value = request.query_params.get(field)
            if value is not None:
                queryset = queryset.filter(**{field: value.lower() in ["1", "true", "yes"]})
        return Response(MissionReplySerializer(queryset, many=True).data)

    def post(self, request, mission_id):
        employee = _get_employee(request)
        mission = get_object_or_404(_mission_queryset_for_employee(employee), id=mission_id)
        serializer = MissionReplyCreateSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        try:
            reply = mission_services.create_mission_reply(
                mission=mission,
                responder=employee,
                content=serializer.validated_data["content"],
                ends_task=serializer.validated_data.get("ends_task", False),
                extra=serializer.validated_data.get("extra"),
            )
        except ValueError as exc:
            return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
        return Response(MissionReplySerializer(reply).data, status=status.HTTP_201_CREATED)


class MissionReopenView(APIView):
    permission_classes = [permissions.IsAuthenticated]

    def post(self, request, mission_id):
        if not request.user.has_perm("mission.reopen_mission"):
            return Response({"detail": "缺少重新打开任务权限"}, status=status.HTTP_403_FORBIDDEN)
        employee = _get_employee(request)
        mission = get_object_or_404(_mission_queryset_for_employee(employee), id=mission_id)
        try:
            mission = mission_services.reopen_mission(mission=mission, reopened_by=employee)
        except ValueError as exc:
            return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
        return Response(MissionSerializer(mission).data)


class MissionCancelView(APIView):
    permission_classes = [permissions.IsAuthenticated]

    def post(self, request, mission_id):
        employee = _get_employee(request)
        mission = get_object_or_404(_mission_queryset_for_employee(employee), id=mission_id)
        try:
            mission = mission_services.cancel_mission(mission=mission, cancelled_by=employee)
        except ValueError as exc:
            return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
        return Response(MissionSerializer(mission).data)


class MissionReplyRejectView(APIView):
    permission_classes = [permissions.IsAuthenticated]

    def post(self, request, reply_id):
        if not request.user.has_perm("mission.reject_mission_reply"):
            return Response({"detail": "缺少撤销任务回应权限"}, status=status.HTTP_403_FORBIDDEN)
        employee = _get_employee(request)
        reply = get_object_or_404(
            mission_models.MissionReply.objects.select_related("mission", "responder", "rejected_by").filter(
                merchant=employee.merchant,
            ),
            id=reply_id,
        )
        try:
            reply = mission_services.reject_reply(reply=reply, rejected_by=employee)
        except ValueError as exc:
            return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
        return Response(MissionReplySerializer(reply).data)


class MissionSetUrgentView(APIView):
    permission_classes = [permissions.IsAuthenticated]

    def post(self, request, mission_id):
        employee = _get_employee(request)
        mission = get_object_or_404(_mission_queryset_for_employee(employee), id=mission_id)
        serializer = MissionUrgentSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        try:
            mission = mission_services.set_mission_urgent(
                mission=mission,
                updated_by=employee,
                is_urgent=serializer.validated_data["is_urgent"],
            )
        except ValueError as exc:
            return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
        return Response(MissionSerializer(mission).data)


class ContentTypeListView(APIView):
    permission_classes = [permissions.IsAuthenticated]

    # Models the frontend is allowed to use as mission content_type targets.
    _ALLOWED_CONTENT_TYPES = [
        ("printing", "plateorder"),
        ("printing", "printingorder"),
        ("printing", "printingjob"),
        ("business", "purchaseorder"),
        ("business", "presalesorder"),
        ("business", "salesorder"),
        ("business", "prepurchaseorder"),
        ("business", "purchasereturnorder"),
        ("business", "salesreturnorder"),
        ("business", "paymentorder"),
        ("business", "receiptorder"),
        ("stock", "transferorder"),
        ("shipment", "shipment"),
        ("stateflow", "process"),
    ]

    # Human-readable labels keyed by (app_label, model).
    _LABELS = {
        ("printing", "plateorder"): "开版单",
        ("printing", "printingorder"): "印刷单",
        ("printing", "printingjob"): "印刷任务",
        ("business", "purchaseorder"): "采购单",
        ("business", "presalesorder"): "预销售单",
        ("business", "salesorder"): "销售单",
        ("business", "prepurchaseorder"): "预采购单",
        ("business", "purchasereturnorder"): "采购退货单",
        ("business", "salesreturnorder"): "销售退货单",
        ("business", "paymentorder"): "付款单",
        ("business", "receiptorder"): "收款单",
        ("stock", "transferorder"): "调拨单",
        ("shipment", "shipment"): "发货单",
        ("stateflow", "process"): "工艺流程实例",
    }

    def get(self, request):
        filters = Q()
        for app_label, model in self._ALLOWED_CONTENT_TYPES:
            filters |= Q(app_label=app_label, model=model)
        cts = ContentType.objects.filter(filters).order_by("app_label", "model")
        data = [
            {
                "id": ct.id,
                "app_label": ct.app_label,
                "model": ct.model,
                "label": f"{ct.app_label}.{ct.model}",
                "name": self._LABELS.get((ct.app_label, ct.model), ct.model),
            }
            for ct in cts
        ]
        return Response(data)
