"""Helpdesk API views.

URL structure (all under /helpdesk/):

  Queues
    GET  /queues/                 — list active queues (agent+)
    POST /queues/                 — create queue (supervisor)
    GET  /queues/<id>/            — queue detail
    PUT  /queues/<id>/            — update queue (supervisor)

  Categories
    GET  /categories/             — list (optional ?queue_id=)
    POST /categories/             — create (supervisor)

  Tags
    GET  /tags/                   — list tenant tags
    POST /tags/                   — create tag (supervisor)

  SLA Policies
    GET  /sla-policies/           — list
    POST /sla-policies/           — create (supervisor)

  Tickets
    GET  /tickets/                — list (scoped: assigned, queue, status filters)
    POST /tickets/                — create ticket
    GET  /tickets/<id>/           — detail
    PATCH /tickets/<id>/          — update fields
    POST  /tickets/<id>/transition/  — change status
    POST  /tickets/<id>/assign/      — assign agent
    POST  /tickets/<id>/escalate/    — escalate

  Replies / Notes
    GET  /tickets/<id>/replies/   — list public replies (+ internal if agent)
    POST /tickets/<id>/replies/   — add reply or internal note

  Activity Feed
    GET  /tickets/<id>/activity/  — list activity feed for ticket

  Attachments
    GET  /tickets/<id>/attachments/            — list attachments
    POST /tickets/<id>/attachments/            — upload attachment
    DELETE /tickets/<id>/attachments/<att_id>/ — remove attachment

  Automations
    GET  /automations/            — list (supervisor)
    POST /automations/            — create (supervisor)
    GET  /automations/<id>/       — detail
    PUT  /automations/<id>/       — update

  Import / Export
    POST /import/tickets/         — import tickets from CSV/Excel
    GET  /export/<entity_type>/   — export entities (CSV, XLSX, PDF)

  Webhooks
    POST /webhooks/incoming/      — receive incoming webhook (creates ticket)

  Email Ingestion
    POST /email-ingestion/        — ingest email into queue

  CSAT
    POST /csat/survey/            — submit satisfaction rating
"""

from __future__ import annotations

from django.db.models import Q
from rest_framework import status
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response

from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiExample

from simorgh.apps.helpdesk.models import (
    AgentTeam,
    AgentTeamMember,
    Automation,
    Category,
    Queue,
    SLAPolicy,
    Tag,
    Ticket,
    TicketReply,
)
from simorgh.apps.helpdesk.serializers import (
    AgentTeamAddMemberSerializer,
    AgentTeamDetailSerializer,
    AgentTeamListSerializer,
    AgentTeamMemberSerializer,
    AgentTeamWriteSerializer,
    AutomationSerializer,
    CategorySerializer,
    QueueCreateSerializer,
    QueueListSerializer,
    SLAPolicySerializer,
    TagSerializer,
    TicketAssignSerializer,
    TicketCreateSerializer,
    TicketDetailSerializer,
    TicketEscalateSerializer,
    TicketListSerializer,
    TicketReplyCreateSerializer,
    TicketReplySerializer,
    TicketTransitionSerializer,
    TicketUpdateSerializer,
)
from simorgh.apps.helpdesk.services import QueueService, TicketService
from simorgh.apps.helpdesk.permissions import (
    PERM_QUEUE_MANAGE,
    PERM_CATEGORY_MANAGE,
    PERM_TAG_MANAGE,
    PERM_SLA_MANAGE,
    PERM_AUTO_MANAGE,
    PERM_TEAM_MANAGE,
    PERM_TICKET_CHANGE,
    PERM_TICKET_DELETE,
    PERM_REPLY_ADD,
)


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _tenant(request: Request):
    tenant = getattr(request, "tenant", None)
    if tenant is None:
        from rest_framework.exceptions import PermissionDenied
        raise PermissionDenied("Tenant required.")
    return tenant


def _org_node_id(request: Request) -> int:
    """Resolve the request actor's primary org-node id."""
    from simorgh.apps.memberships.models import Membership, MembershipStatus
    m = (
        Membership.objects.filter(
            tenant=_tenant(request),
            users=request.user,
            status=MembershipStatus.ACTIVE,
        )
        .order_by("id")
        .first()
    )
    if m is not None:
        return m.organization_node_id
    # Superusers without an explicit membership fall back to the tenant's root node.
    if getattr(request.user, "is_superuser", False):
        from simorgh.apps.organizations.models import OrganizationNode
        root = (
            OrganizationNode.objects.filter(tenant=_tenant(request))
            .order_by("id")
            .first()
        )
        if root is not None:
            return root.pk
    from rest_framework.exceptions import PermissionDenied
    raise PermissionDenied("No active membership for this tenant.")


def _get_ticket_or_404(pk: int, request: Request) -> Ticket:
    tenant = _tenant(request)
    try:
        return Ticket.objects.select_related(
            "queue", "category", "sla_policy",
            "requester", "assigned_to", "escalated_to",
        ).get(pk=pk, tenant=tenant)
    except Ticket.DoesNotExist:
        from rest_framework.exceptions import NotFound
        raise NotFound("Ticket not found.")


def _is_agent(request: Request) -> bool:
    """Return True if the user has agent-level permissions (can see internal notes etc.)."""
    from simorgh.core.context import current_request_context
    ctx = current_request_context()
    if ctx.is_superuser:
        return True
    return PERM_TICKET_CHANGE in (ctx.permissions or set())


def _require_perm(request: Request, codename: str) -> None:
    """Raise PermissionDenied if the current actor lacks ``codename``."""
    from rest_framework.exceptions import PermissionDenied
    from simorgh.core.context import current_request_context
    ctx = current_request_context()
    if ctx.is_superuser:
        return
    if codename not in (ctx.permissions or set()):
        raise PermissionDenied(f"Permission required: {codename}")


# ---------------------------------------------------------------------------
# Queues
# ---------------------------------------------------------------------------

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def queues_list(request: Request) -> Response:
    tenant = _tenant(request)
    if request.method == "GET":
        qs = Queue.objects.filter(tenant=tenant, is_active=True).order_by("sort_order", "name")
        return Response(QueueListSerializer(qs, many=True).data)

    # POST — supervisor only
    _require_perm(request, PERM_QUEUE_MANAGE)
    ser = QueueCreateSerializer(data=request.data)
    ser.is_valid(raise_exception=True)
    d = ser.validated_data
    queue = QueueService.create(
        tenant_id=tenant.pk,
        organization_node_id=_org_node_id(request),
        name=d["name"],
        description=d.get("description", ""),
        email_address=d.get("email_address", ""),
        is_default=d.get("is_default", False),
        default_sla_policy_id=d.get("default_sla_policy_id"),
        created_by_id=request.user.pk,
    )
    return Response(QueueListSerializer(queue).data, status=status.HTTP_201_CREATED)


@api_view(["GET", "PUT"])
@permission_classes([IsAuthenticated])
def queue_detail(request: Request, pk: int) -> Response:
    tenant = _tenant(request)
    try:
        queue = Queue.objects.get(pk=pk, tenant=tenant)
    except Queue.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    if request.method == "GET":
        return Response(QueueListSerializer(queue).data)

    # PUT — supervisor only
    _require_perm(request, PERM_QUEUE_MANAGE)
    ser = QueueCreateSerializer(data=request.data, partial=True)
    ser.is_valid(raise_exception=True)
    d = ser.validated_data
    for field, value in d.items():
        setattr(queue, field, value)
    queue.save()
    return Response(QueueListSerializer(queue).data)


# ---------------------------------------------------------------------------
# Categories
# ---------------------------------------------------------------------------

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def categories_list(request: Request) -> Response:
    tenant = _tenant(request)
    if request.method == "GET":
        qs = Category.objects.filter(tenant=tenant, is_active=True)
        queue_id = request.query_params.get("queue_id")
        if queue_id:
            qs = qs.filter(Q(queue_id=queue_id) | Q(queue__isnull=True))
        return Response(CategorySerializer(qs, many=True).data)

    # POST — supervisor only
    _require_perm(request, PERM_CATEGORY_MANAGE)
    ser = CategorySerializer(data=request.data)
    ser.is_valid(raise_exception=True)
    cat = ser.save(
        tenant=_tenant(request),
        organization_node_id=_org_node_id(request),
    )
    return Response(CategorySerializer(cat).data, status=status.HTTP_201_CREATED)


# ---------------------------------------------------------------------------
# Tags
# ---------------------------------------------------------------------------

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def tags_list(request: Request) -> Response:
    tenant = _tenant(request)
    if request.method == "GET":
        qs = Tag.objects.filter(tenant=tenant, is_active=True)
        return Response(TagSerializer(qs, many=True).data)

    # POST — supervisor only
    _require_perm(request, PERM_TAG_MANAGE)
    ser = TagSerializer(data=request.data)
    ser.is_valid(raise_exception=True)
    tag = ser.save(tenant=tenant)
    return Response(TagSerializer(tag).data, status=status.HTTP_201_CREATED)


# ---------------------------------------------------------------------------
# SLA Policies
# ---------------------------------------------------------------------------

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def sla_policies_list(request: Request) -> Response:
    tenant = _tenant(request)
    if request.method == "GET":
        qs = SLAPolicy.objects.filter(tenant=tenant, is_active=True)
        return Response(SLAPolicySerializer(qs, many=True).data)

    # POST — supervisor only
    _require_perm(request, PERM_SLA_MANAGE)
    ser = SLAPolicySerializer(data=request.data)
    ser.is_valid(raise_exception=True)
    policy = ser.save(
        tenant=tenant,
        organization_node_id=_org_node_id(request),
    )
    return Response(SLAPolicySerializer(policy).data, status=status.HTTP_201_CREATED)


# ---------------------------------------------------------------------------
# Tickets
# ---------------------------------------------------------------------------

@extend_schema(
    description="List tickets with filtering and pagination. Supports offset and cursor-based pagination.",
    parameters=[
        OpenApiParameter(name="status", description="Filter by status (comma-separated values)", required=False, type=str),
        OpenApiParameter(name="priority", description="Filter by priority", required=False, type=str),
        OpenApiParameter(name="queue_id", description="Filter by queue ID", required=False, type=int),
        OpenApiParameter(name="assigned_to_me", description="Filter tickets assigned to the current user (1=true)", required=False, type=str),
        OpenApiParameter(name="q", description="Full-text search across subject, requester email, and requester name", required=False, type=str),
        OpenApiParameter(name="paginator", description="Pagination mode: 'cursor' for cursor-based or omit for offset", required=False, type=str),
        OpenApiParameter(name="page", description="Page number (offset pagination)", required=False, type=int),
        OpenApiParameter(name="page_size", description="Items per page (max 100)", required=False, type=int),
    ],
    methods=["GET"],
    responses={200: TicketListSerializer(many=True)},
)
@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def tickets_list(request: Request) -> Response:
    tenant = _tenant(request)

    if request.method == "GET":
        qs = Ticket.objects.filter(tenant=tenant).select_related(
            "queue", "category", "requester", "assigned_to"
        ).prefetch_related("tags")

        # Portal users may only see their own tickets.
        if not _is_agent(request):
            qs = qs.filter(requester=request.user)

        # Filters.
        q_status = request.query_params.get("status")
        q_priority = request.query_params.get("priority")
        q_queue = request.query_params.get("queue_id")
        q_assigned = request.query_params.get("assigned_to_me")
        q_search = request.query_params.get("q")

        if q_status:
            qs = qs.filter(status__in=q_status.split(","))
        if q_priority:
            qs = qs.filter(priority=q_priority)
        if q_queue:
            qs = qs.filter(queue_id=q_queue)
        if q_assigned == "1":
            qs = qs.filter(assigned_to=request.user)
        if q_search:
            qs = qs.filter(
                Q(subject__icontains=q_search)
                | Q(requester_email__icontains=q_search)
                | Q(requester_name__icontains=q_search)
            )

        qs = qs.order_by("-created_at")

        # Support both cursor-based and offset-based pagination.
        use_cursor = request.query_params.get("paginator") == "cursor"
        if use_cursor:
            from simorgh.core.api.pagination import StandardCursorPagination
            paginator = StandardCursorPagination()
            page = paginator.paginate_queryset(qs, request)
            if page is not None:
                return paginator.get_paginated_response(
                    TicketListSerializer(page, many=True).data
                )

        # Default: offset pagination (backward compatible)
        page = max(1, int(request.query_params.get("page", 1)))
        page_size = min(100, max(1, int(request.query_params.get("page_size", 20))))
        offset = (page - 1) * page_size
        total = qs.count()
        items = qs[offset: offset + page_size]

        return Response({
            "total": total,
            "page": page,
            "page_size": page_size,
            "results": TicketListSerializer(items, many=True).data,
        })

    # POST
    ser = TicketCreateSerializer(data=request.data)
    ser.is_valid(raise_exception=True)
    d = ser.validated_data

    # Resolve queue: fall back to tenant's default queue when not provided.
    queue_id = d.get("queue_id")
    if not queue_id:
        default_q = (
            Queue.objects.filter(tenant=tenant, is_default=True, is_active=True).first()
            or Queue.objects.filter(tenant=tenant, is_active=True).order_by("sort_order", "id").first()
        )
        if default_q is None:
            from rest_framework.exceptions import ValidationError
            raise ValidationError("No active queue found for this tenant. Please contact your administrator.")
        queue_id = default_q.pk

    # Supervisors may specify a different requester; everyone else uses themselves.
    from simorgh.core.context import current_request_context
    ctx = current_request_context()
    is_supervisor = ctx.is_superuser or PERM_QUEUE_MANAGE in (ctx.permissions or set())
    requester_id = request.user.pk
    if is_supervisor and d.get("requester_id"):
        requester_id = d["requester_id"]

    ticket = TicketService.create(
        tenant_id=tenant.pk,
        organization_node_id=_org_node_id(request),
        queue_id=queue_id,
        subject=d["subject"],
        description=d.get("description", ""),
        priority=d.get("priority", "normal"),
        channel=d.get("channel", "web"),
        category_id=d.get("category_id"),
        requester_id=requester_id,
        requester_email=d.get("requester_email", ""),
        requester_name=d.get("requester_name", ""),
        created_by_id=request.user.pk,
        custom_fields=d.get("custom_fields", {}),
        tag_ids=d.get("tag_ids", []),
        actor=request.user,
    )
    return Response(TicketDetailSerializer(ticket).data, status=status.HTTP_201_CREATED)


@api_view(["GET", "PATCH"])
@permission_classes([IsAuthenticated])
def ticket_detail(request: Request, pk: int) -> Response:
    ticket = _get_ticket_or_404(pk, request)

    # Portal users may only access their own tickets.
    if not _is_agent(request) and ticket.requester_id != request.user.pk:
        from rest_framework.exceptions import NotFound
        raise NotFound("Ticket not found.")

    if request.method == "GET":
        return Response(TicketDetailSerializer(ticket).data)

    # PATCH — agent+ only
    _require_perm(request, PERM_TICKET_CHANGE)
    ser = TicketUpdateSerializer(data=request.data, partial=True)
    ser.is_valid(raise_exception=True)
    TicketService.update(ticket, actor_id=request.user.pk, actor=request.user, **ser.validated_data)
    ticket.refresh_from_db()
    return Response(TicketDetailSerializer(ticket).data)


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def ticket_transition(request: Request, pk: int) -> Response:
    ticket = _get_ticket_or_404(pk, request)
    _require_perm(request, PERM_TICKET_CHANGE)
    ser = TicketTransitionSerializer(data=request.data)
    ser.is_valid(raise_exception=True)
    TicketService.transition(
        ticket,
        new_status=ser.validated_data["status"],
        actor_id=request.user.pk,
        actor=request.user,
        note=ser.validated_data.get("note", ""),
    )
    ticket.refresh_from_db()
    return Response(TicketDetailSerializer(ticket).data)


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def ticket_assign(request: Request, pk: int) -> Response:
    ticket = _get_ticket_or_404(pk, request)
    _require_perm(request, PERM_TICKET_CHANGE)

    # Supervisors (PERM_QUEUE_MANAGE or superuser) can always reassign.
    # Regular agents may only reassign when the tenant setting allows it.
    from simorgh.core.context import current_request_context as _ctx
    from simorgh.apps.app_settings.services import resolve as _resolve
    _c = _ctx()
    _is_supervisor = _c.is_superuser or (PERM_QUEUE_MANAGE in (_c.permissions or set()))
    if not _is_supervisor:
        _t = _tenant(request)
        if not _resolve("helpdesk.agent_reassign", tenant_id=_t.pk):
            from rest_framework.exceptions import PermissionDenied as _PD
            raise _PD("Agent reassignment is not enabled for this tenant.")

    ser = TicketAssignSerializer(data=request.data)
    ser.is_valid(raise_exception=True)
    TicketService.assign(
        ticket,
        assigned_to_id=ser.validated_data["assigned_to_id"],
        assigned_by_id=request.user.pk,
        actor=request.user,
    )
    ticket.refresh_from_db()
    return Response(TicketDetailSerializer(ticket).data)


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def ticket_escalate(request: Request, pk: int) -> Response:
    ticket = _get_ticket_or_404(pk, request)
    _require_perm(request, PERM_TICKET_CHANGE)
    ser = TicketEscalateSerializer(data=request.data)
    ser.is_valid(raise_exception=True)
    TicketService.escalate(
        ticket,
        escalated_to_id=ser.validated_data["escalated_to_id"],
        reason=ser.validated_data.get("reason", ""),
        actor_id=request.user.pk,
        actor=request.user,
    )
    ticket.refresh_from_db()
    return Response(TicketDetailSerializer(ticket).data)


# ---------------------------------------------------------------------------
# Replies / notes
# ---------------------------------------------------------------------------

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def ticket_replies(request: Request, pk: int) -> Response:
    ticket = _get_ticket_or_404(pk, request)

    # Portal users may only access their own tickets.
    if not _is_agent(request) and ticket.requester_id != request.user.pk:
        from rest_framework.exceptions import NotFound
        raise NotFound("Ticket not found.")

    if request.method == "GET":
        qs = TicketReply.objects.filter(ticket=ticket)
        # Internal notes are only visible to agents and supervisors.
        if not _is_agent(request):
            qs = qs.filter(is_internal=False)
        qs = qs.order_by("created_at")
        return Response(TicketReplySerializer(qs, many=True).data)

    # POST
    ser = TicketReplyCreateSerializer(data=request.data)
    ser.is_valid(raise_exception=True)
    d = ser.validated_data

    # Portal users cannot post internal notes.
    is_internal = d.get("is_internal", False)
    if is_internal and not _is_agent(request):
        from rest_framework.exceptions import PermissionDenied
        raise PermissionDenied("Only agents can post internal notes.")

    reply = TicketService.add_reply(
        ticket,
        body=d["body"],
        is_internal=is_internal,
        author_id=request.user.pk,
        author_email=d.get("author_email", ""),
        author_name=d.get("author_name", ""),
        channel=d.get("channel", "web"),
        actor=request.user,
    )
    return Response(TicketReplySerializer(reply).data, status=status.HTTP_201_CREATED)


# ---------------------------------------------------------------------------
# Activity feed
# ---------------------------------------------------------------------------

@api_view(["GET"])
@permission_classes([IsAuthenticated])
def ticket_activity(request: Request, pk: int) -> Response:
    """Return the platform_core activity feed for a ticket."""
    from simorgh.apps.platform_core.services import activity_for_entity

    ticket = _get_ticket_or_404(pk, request)

    # Portal users may only see activity on their own tickets.
    if not _is_agent(request) and ticket.requester_id != request.user.pk:
        from rest_framework.exceptions import NotFound
        raise NotFound("Ticket not found.")

    limit = min(200, max(1, int(request.query_params.get("limit", 50))))
    activities = activity_for_entity(ticket, limit=limit)

    data = [
        {
            "id": str(a.pk),
            "verb": a.verb,
            "actor": {
                "id": a.actor_id,
                "name": str(a.actor) if a.actor else None,
            } if a.actor_id else None,
            "occurred_at": a.occurred_at.isoformat(),
            "extra": a.extra,
        }
        for a in activities
    ]
    return Response(data)


# ---------------------------------------------------------------------------
# Attachments
# ---------------------------------------------------------------------------

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def ticket_attachments(request: Request, pk: int) -> Response:
    """List or upload attachments for a ticket."""
    from simorgh.apps.platform_core.services import attach_file, list_attachments
    from simorgh.apps.storage.services import StorageError, store_file

    ticket = _get_ticket_or_404(pk, request)

    # Portal users may only access their own tickets.
    if not _is_agent(request) and ticket.requester_id != request.user.pk:
        from rest_framework.exceptions import NotFound
        raise NotFound("Ticket not found.")

    if request.method == "GET":
        attachments = list_attachments(ticket)
        data = [
            {
                "id": str(att.pk),
                "filename": att.file.filename,
                "content_type": att.file.content_type,
                "size_bytes": att.file.size_bytes,
                "kind": att.kind,
                "description": att.description,
                "uploaded_by": att.uploaded_by_id,
                "created_at": att.created_at.isoformat(),
                "url": att.file.path,
            }
            for att in attachments
        ]
        return Response(data)

    # POST — upload a new attachment.
    uploaded_file = request.FILES.get("file")
    if not uploaded_file:
        return Response({"detail": "No file provided."}, status=status.HTTP_400_BAD_REQUEST)

    # Enforce the module's max_attachment_bytes setting.
    from simorgh.apps.app_settings.services import resolve as resolve_setting
    max_bytes = resolve_setting("helpdesk.max_attachment_bytes", tenant_id=ticket.tenant_id)

    try:
        file_meta = store_file(
            filename=uploaded_file.name,
            content=uploaded_file,
            content_type=uploaded_file.content_type or "",
            tenant_id=ticket.tenant_id,
            organization_node_id=ticket.organization_node_id,
            uploaded_by_id=request.user.pk,
        )
    except StorageError as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)

    # Enforce per-module limit (storage service enforces global; we enforce module limit here).
    if max_bytes and file_meta.size_bytes > max_bytes:
        file_meta.delete()
        return Response(
            {"detail": f"File exceeds helpdesk max size of {max_bytes} bytes."},
            status=status.HTTP_400_BAD_REQUEST,
        )

    att = attach_file(
        ticket,
        file_meta,
        description=request.data.get("description", ""),
        uploaded_by=request.user,
    )
    return Response(
        {
            "id": str(att.pk),
            "filename": file_meta.filename,
            "content_type": file_meta.content_type,
            "size_bytes": file_meta.size_bytes,
            "kind": att.kind,
            "description": att.description,
            "created_at": att.created_at.isoformat(),
        },
        status=status.HTTP_201_CREATED,
    )


@api_view(["DELETE"])
@permission_classes([IsAuthenticated])
def ticket_attachment_detail(request: Request, pk: int, att_id: int) -> Response:
    """Remove an attachment from a ticket."""
    from simorgh.apps.platform_core.models import Attachment
    from simorgh.apps.platform_core.services import remove_attachment
    from django.contrib.contenttypes.models import ContentType

    ticket = _get_ticket_or_404(pk, request)
    _require_perm(request, PERM_TICKET_CHANGE)

    ct = ContentType.objects.get_for_model(Ticket)
    try:
        att = Attachment.objects.get(pk=att_id, content_type=ct, object_id=str(ticket.pk))
    except Attachment.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    remove_attachment(att, actor=request.user)
    return Response(status=status.HTTP_204_NO_CONTENT)


# ---------------------------------------------------------------------------
# Automations
# ---------------------------------------------------------------------------

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def automations_list(request: Request) -> Response:
    tenant = _tenant(request)

    if request.method == "GET":
        qs = Automation.objects.filter(tenant=tenant).order_by("sort_order", "name")
        return Response(AutomationSerializer(qs, many=True).data)

    # POST — supervisor only
    _require_perm(request, PERM_AUTO_MANAGE)
    ser = AutomationSerializer(data=request.data)
    ser.is_valid(raise_exception=True)
    auto = ser.save(
        tenant=tenant,
        organization_node_id=_org_node_id(request),
    )
    return Response(AutomationSerializer(auto).data, status=status.HTTP_201_CREATED)


@api_view(["GET", "PUT", "PATCH"])
@permission_classes([IsAuthenticated])
def automation_detail(request: Request, pk: int) -> Response:
    tenant = _tenant(request)
    try:
        auto = Automation.objects.get(pk=pk, tenant=tenant)
    except Automation.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    if request.method == "GET":
        return Response(AutomationSerializer(auto).data)

    # PUT/PATCH — supervisor only
    _require_perm(request, PERM_AUTO_MANAGE)
    ser = AutomationSerializer(auto, data=request.data, partial=request.method == "PATCH")
    ser.is_valid(raise_exception=True)
    ser.save()
    return Response(AutomationSerializer(auto).data)


# ---------------------------------------------------------------------------
# Workspace users — supervisor picker
# ---------------------------------------------------------------------------

@api_view(["GET"])
@permission_classes([IsAuthenticated])
def helpdesk_users_list(request: Request) -> Response:
    """Return workspace members for the requester picker (supervisor only).

    Each entry includes ``id``, ``first_name``, ``last_name``, ``email``,
    and ``phone`` (sourced from the user's profile when available).
    """
    _require_perm(request, PERM_QUEUE_MANAGE)
    tenant = _tenant(request)

    from django.contrib.auth import get_user_model

    User = get_user_model()
    users = (
        User.objects.filter(is_active=True)
        .select_related("profile")
        .order_by("first_name", "last_name", "email")
    )

    data = [
        {
            "id": u.pk,
            "first_name": u.first_name,
            "last_name": u.last_name,
            "email": u.email,
            "phone": getattr(getattr(u, "profile", None), "phone", ""),
        }
        for u in users
    ]
    return Response(data)


# ---------------------------------------------------------------------------
# Settings
# ---------------------------------------------------------------------------

_HELPDESK_SETTING_KEYS = [
    "helpdesk.auto_close_hours",
    "helpdesk.cross_team_notes",
    "helpdesk.max_attachment_bytes",
    "helpdesk.csat_delay_hours",
    "helpdesk.portal_anonymous",
    "helpdesk.default_queue_id",
    "helpdesk.agent_reassign",
    "helpdesk.notify_sms",
]


@api_view(["GET", "PATCH"])
@permission_classes([IsAuthenticated])
def helpdesk_settings(request: Request) -> Response:
    """GET: return current helpdesk settings values.
    PATCH: update one or more settings (supervisor only).
    """
    from simorgh.apps.app_settings import services as settings_svc
    from simorgh.apps.app_settings.registry import get_definition

    if request.method == "PATCH":
        _require_perm(request, PERM_QUEUE_MANAGE)

    tenant = _tenant(request)

    if request.method == "GET":
        data = {}
        for key in _HELPDESK_SETTING_KEYS:
            data[key] = settings_svc.resolve(key, tenant_id=tenant.id)
        return Response(data)

    # PATCH
    errors = {}
    for key, value in request.data.items():
        if key not in _HELPDESK_SETTING_KEYS:
            errors[key] = "Unknown setting key."
            continue
        try:
            settings_svc.set_value(
                key,
                value,
                scope="tenant",
                tenant_id=tenant.id,
                updated_by_id=request.user.pk,
            )
        except Exception as exc:
            errors[key] = str(exc)

    if errors:
        return Response({"errors": errors}, status=status.HTTP_400_BAD_REQUEST)

    data = {}
    for key in _HELPDESK_SETTING_KEYS:
        data[key] = settings_svc.resolve(key, tenant_id=tenant.id)
    return Response(data)


# ---------------------------------------------------------------------------
# Agent Teams
# ---------------------------------------------------------------------------

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def teams_list(request: Request) -> Response:
    """GET: list agent teams. POST: create a new team (supervisor)."""
    tenant = _tenant(request)
    if request.method == "POST":
        _require_perm(request, PERM_TEAM_MANAGE)
        ser = AgentTeamWriteSerializer(data=request.data)
        ser.is_valid(raise_exception=True)
        d = ser.validated_data
        category_ids = d.pop("category_ids", [])
        org_node_id = _org_node_id(request)
        team = AgentTeam.objects.create(
            tenant=tenant,
            organization_node_id=org_node_id,
            **d,
        )
        if category_ids:
            team.categories.set(
                Category.objects.filter(tenant=tenant, pk__in=category_ids)
            )
        return Response(
            AgentTeamDetailSerializer(team).data,
            status=status.HTTP_201_CREATED,
        )

    qs = AgentTeam.objects.filter(tenant=tenant).prefetch_related("memberships", "categories")
    return Response(AgentTeamListSerializer(qs, many=True).data)


@api_view(["GET", "PATCH", "DELETE"])
@permission_classes([IsAuthenticated])
def team_detail(request: Request, pk: int) -> Response:
    """GET/PATCH/DELETE a single agent team."""
    tenant = _tenant(request)
    try:
        team = AgentTeam.objects.prefetch_related("memberships__user", "categories").get(
            pk=pk, tenant=tenant
        )
    except AgentTeam.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    if request.method == "GET":
        return Response(AgentTeamDetailSerializer(team).data)

    _require_perm(request, PERM_TEAM_MANAGE)

    if request.method == "DELETE":
        team.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

    # PATCH
    ser = AgentTeamWriteSerializer(data=request.data, partial=True)
    ser.is_valid(raise_exception=True)
    d = ser.validated_data
    category_ids = d.pop("category_ids", None)
    for attr, value in d.items():
        setattr(team, attr, value)
    team.save()
    if category_ids is not None:
        team.categories.set(
            Category.objects.filter(tenant=tenant, pk__in=category_ids)
        )
    team.refresh_from_db()
    return Response(AgentTeamDetailSerializer(team).data)


@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def team_members(request: Request, pk: int) -> Response:
    """GET: list members. POST: add a member (supervisor)."""
    tenant = _tenant(request)
    try:
        team = AgentTeam.objects.get(pk=pk, tenant=tenant)
    except AgentTeam.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    if request.method == "GET":
        members = AgentTeamMember.objects.select_related("user").filter(team=team)
        return Response(AgentTeamMemberSerializer(members, many=True).data)

    _require_perm(request, PERM_TEAM_MANAGE)
    ser = AgentTeamAddMemberSerializer(data=request.data)
    ser.is_valid(raise_exception=True)
    from django.contrib.auth import get_user_model
    User = get_user_model()
    try:
        user = User.objects.get(pk=ser.validated_data["user_id"])
    except User.DoesNotExist:
        return Response({"user_id": "User not found."}, status=status.HTTP_400_BAD_REQUEST)
    member, created = AgentTeamMember.objects.get_or_create(
        team=team,
        user=user,
        defaults={
            "role": ser.validated_data["role"],
            "added_by": request.user,
        },
    )
    if not created:
        # Update role if already a member
        member.role = ser.validated_data["role"]
        member.save(update_fields=["role"])
    return Response(
        AgentTeamMemberSerializer(member).data,
        status=status.HTTP_201_CREATED if created else status.HTTP_200_OK,
    )


@api_view(["DELETE"])
@permission_classes([IsAuthenticated])
def team_member_detail(request: Request, pk: int, user_id: int) -> Response:
    """Remove a user from a team."""
    tenant = _tenant(request)
    _require_perm(request, PERM_TEAM_MANAGE)
    try:
        team = AgentTeam.objects.get(pk=pk, tenant=tenant)
    except AgentTeam.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)
    deleted, _ = AgentTeamMember.objects.filter(team=team, user_id=user_id).delete()
    if not deleted:
        return Response(status=status.HTTP_404_NOT_FOUND)
    return Response(status=status.HTTP_204_NO_CONTENT)


# ---------------------------------------------------------------------------
# Reports
# ---------------------------------------------------------------------------

@api_view(["GET"])
@permission_classes([IsAuthenticated])
def reports_summary(request: Request) -> Response:
    """GET /helpdesk/reports/summary/

    Returns aggregate ticket counts per queue plus overall totals.
    Requires helpdesk.report.view permission (or superuser).
    """
    from django.db.models import Count, Q as DQ
    from simorgh.apps.helpdesk.models import TicketStatus

    _require_perm(request, PERM_TICKET_CHANGE)
    tenant = _tenant(request)

    now = __import__("django.utils.timezone", fromlist=["now"]).now()

    # Aggregate per queue
    queue_qs = (
        Queue.objects.filter(tenant=tenant, is_active=True)
        .annotate(
            total=Count("tickets", filter=DQ(tickets__is_deleted=False)),
            new_count=Count(
                "tickets",
                filter=DQ(tickets__status=TicketStatus.NEW, tickets__is_deleted=False),
            ),
            open_count=Count(
                "tickets",
                filter=DQ(tickets__status=TicketStatus.OPEN, tickets__is_deleted=False),
            ),
            pending_count=Count(
                "tickets",
                filter=DQ(tickets__status=TicketStatus.PENDING, tickets__is_deleted=False),
            ),
            solved_count=Count(
                "tickets",
                filter=DQ(tickets__status=TicketStatus.SOLVED, tickets__is_deleted=False),
            ),
            closed_count=Count(
                "tickets",
                filter=DQ(tickets__status=TicketStatus.CLOSED, tickets__is_deleted=False),
            ),
            sla_breached=Count(
                "tickets",
                filter=DQ(
                    tickets__is_deleted=False,
                    tickets__resolution_due_at__lt=now,
                    tickets__resolved_at__isnull=True,
                ),
            ),
        )
        .order_by("sort_order", "name")
    )

    queues_data = [
        {
            "queue_id": q.pk,
            "queue_name": q.name,
            "total": q.total,
            "new": q.new_count,
            "open": q.open_count,
            "pending": q.pending_count,
            "solved": q.solved_count,
            "closed": q.closed_count,
            "sla_breached": q.sla_breached,
        }
        for q in queue_qs
    ]

    # Overall totals
    from simorgh.apps.helpdesk.models import Ticket as _Ticket
    base = _Ticket.objects.filter(tenant=tenant)
    totals = {
        "total": base.count(),
        "new": base.filter(status=TicketStatus.NEW).count(),
        "open": base.filter(status=TicketStatus.OPEN).count(),
        "pending": base.filter(status=TicketStatus.PENDING).count(),
        "solved": base.filter(status=TicketStatus.SOLVED).count(),
        "closed": base.filter(status=TicketStatus.CLOSED).count(),
        "sla_breached": base.filter(
            resolution_due_at__lt=now,
            resolved_at__isnull=True,
        ).count(),
    }

    return Response({"queues": queues_data, "totals": totals})


# ---------------------------------------------------------------------------
# Import
# ---------------------------------------------------------------------------

@extend_schema(
    description="Import tickets from a CSV or Excel (XLSX) file. Processing happens asynchronously.",
    request={"multipart/form-data": {"type": "object", "properties": {"file": {"type": "string", "format": "binary"}, "queue_id": {"type": "integer"}, "dry_run": {"type": "string"}}}},
    responses={202: {"type": "object", "properties": {"task_id": {"type": "string"}, "total_rows": {"type": "integer"}, "status": {"type": "string"}}}},
)
@api_view(["POST"])
@permission_classes([IsAuthenticated])
def import_tickets(request: Request) -> Response:
    """Import tickets from a CSV or Excel file.

    Accepts multipart/form-data with a ``file`` field (.csv or .xlsx).
    Optional ``queue_id`` field to route all imported tickets to a
    specific queue.  Processing happens asynchronously via Celery.
    """
    _require_perm(request, PERM_QUEUE_MANAGE)
    tenant = _tenant(request)
    org_node_id = _org_node_id(request)

    uploaded = request.FILES.get("file")
    if not uploaded:
        return Response({"detail": "No file provided."}, status=status.HTTP_400_BAD_REQUEST)

    filename = uploaded.name.lower()
    if not (filename.endswith(".csv") or filename.endswith(".xlsx") or filename.endswith(".xls")):
        return Response(
            {"detail": "Unsupported file format. Use .csv or .xlsx."},
            status=status.HTTP_400_BAD_REQUEST,
        )

    file_content = uploaded.read()
    try:
        if filename.endswith(".csv"):
            from simorgh.apps.helpdesk.import_export import parse_csv_file
            rows = parse_csv_file(file_content)
        else:
            from simorgh.apps.helpdesk.import_export import parse_excel_file
            rows = parse_excel_file(file_content)
    except Exception as exc:
        return Response(
            {"detail": f"Failed to parse file: {exc}"},
            status=status.HTTP_400_BAD_REQUEST,
        )

    if not rows:
        return Response({"detail": "No data rows found in file."}, status=status.HTTP_400_BAD_REQUEST)

    queue_id = request.data.get("queue_id") or request.query_params.get("queue_id")
    if queue_id:
        try:
            queue_id = int(queue_id)
        except (TypeError, ValueError):
            return Response({"detail": "Invalid queue_id."}, status=status.HTTP_400_BAD_REQUEST)

    dry_run = request.data.get("dry_run") == "true"
    if dry_run:
        preview = []
        for idx, row in enumerate(rows[:20], start=1):
            preview.append({"row": idx, "fields": {k: str(v)[:120] for k, v in row.items()}})
        return Response({"total_rows": len(rows), "preview": preview})

    from simorgh.apps.helpdesk.import_export import process_ticket_import
    task = process_ticket_import.delay(
        tenant_id=tenant.pk,
        org_node_id=org_node_id,
        actor_id=request.user.pk,
        rows=rows,
        queue_id=queue_id,
    )

    return Response({
        "task_id": task.id,
        "total_rows": len(rows),
        "status": "processing",
    }, status=status.HTTP_202_ACCEPTED)


# ---------------------------------------------------------------------------
# Export
# ---------------------------------------------------------------------------

@extend_schema(
    description="Export helpdesk entities (ticket, reply) in CSV, XLSX, or PDF format. Uses the platform ExportRegistry.",
    parameters=[
        OpenApiParameter(name="format", description="Export format: csv, xlsx, or pdf", required=False, type=str, default="csv"),
        OpenApiParameter(name="status", description="Filter tickets by status", required=False, type=str),
        OpenApiParameter(name="queue_id", description="Filter by queue ID", required=False, type=int),
    ],
    responses={200: {"type": "string", "format": "binary"}},
)
@api_view(["GET"])
@permission_classes([IsAuthenticated])
def export_entity(request: Request, entity_type: str) -> Response:
    """GET /helpdesk/export/<entity_type>/?format=csv|xlsx|pdf

    Exports entities registered with the ExportRegistry.  Supported formats:
    csv, xlsx (Excel), pdf.  All query parameters are forwarded as filters
    to the registered queryset_fn.
    """
    from simorgh.apps.platform_core.export_registry import export_registry

    tenant = _tenant(request)
    spec = export_registry.get(entity_type)
    if spec is None:
        return Response(
            {"detail": f"No export registered for entity type '{entity_type}'."},
            status=status.HTTP_400_BAD_REQUEST,
        )

    fmt = request.query_params.get("format", "csv").lower()
    if fmt not in ("csv", "xlsx", "excel", "pdf"):
        return Response(
            {"detail": "Unsupported format. Use csv, xlsx, or pdf."},
            status=status.HTTP_400_BAD_REQUEST,
        )

    filters = {k: v for k, v in request.query_params.items() if k not in ("format", "cursor", "page", "page_size")}
    objects = spec.queryset_fn(tenant=tenant, actor=request.user, filters=filters)
    results = [export_registry.build_row(spec, obj) for obj in objects]

    from simorgh.apps.helpdesk.import_export import generate_csv, generate_excel, generate_pdf

    if fmt == "csv":
        content = generate_csv(spec, results)
        content_type = "text/csv"
        ext = "csv"
    elif fmt in ("xlsx", "excel"):
        content = generate_excel(spec, results)
        content_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
        ext = "xlsx"
    else:
        title = spec.display_name or entity_type
        content = generate_pdf(spec, results, title=title)
        content_type = "application/pdf"
        ext = "pdf"

    disposition = f'attachment; filename="{entity_type.replace(".", "_")}_export.{ext}"'
    return Response(
        content,
        content_type=content_type,
        headers={"Content-Disposition": disposition},
    )


# ---------------------------------------------------------------------------
# Webhooks — Incoming
# ---------------------------------------------------------------------------

@extend_schema(
    description="Receive an incoming webhook payload and create a helpdesk ticket. Supports HMAC signature verification via X-Helpdesk-Secret header.",
    request={"application/json": {"type": "object", "required": ["subject"], "properties": {"queue_id": {"type": "integer"}, "subject": {"type": "string"}, "description": {"type": "string"}, "priority": {"type": "string", "default": "normal"}, "requester_email": {"type": "string"}, "requester_name": {"type": "string"}, "custom_fields": {"type": "object"}, "tags": {"type": "array", "items": {"type": "string"}}}}},
    responses={201: {"type": "object", "properties": {"ticket_id": {"type": "integer"}, "reference_number": {"type": "string"}, "public_id": {"type": "string"}}}},
)
@api_view(["POST"])
@permission_classes([IsAuthenticated])
def webhook_incoming(request: Request) -> Response:
    """Receive an incoming webhook payload and create a ticket.

    Request body (JSON)::
        {
            "queue_id": 1,                     // optional; falls back to default
            "subject": "Issue from external system",
            "description": "...",
            "priority": "normal",               // optional
            "requester_email": "user@example.com",
            "requester_name": "John Doe",
            "custom_fields": {},                // optional
            "tags": ["tag1", "tag2"]            // optional tag names
        }

    Also supports X-Helpdesk-Secret header for HMAC validation when
    ``helpdesk.webhook_secret`` app setting is configured.
    """
    tenant = _tenant(request)
    org_node_id = _org_node_id(request)

    payload = request.data
    if not isinstance(payload, dict):
        return Response({"detail": "JSON object expected."}, status=status.HTTP_400_BAD_REQUEST)

    subject = payload.get("subject")
    if not subject:
        return Response({"detail": "subject is required."}, status=status.HTTP_400_BAD_REQUEST)

    from simorgh.apps.helpdesk.webhooks import process_incoming_ticket

    tag_ids = []
    tag_names = payload.get("tags", [])
    if tag_names:
        from simorgh.apps.helpdesk.models import Tag
        tag_ids = list(
            Tag.objects.filter(
                tenant=tenant, name__in=tag_names, is_active=True
            ).values_list("pk", flat=True)
        )

    try:
        result = process_incoming_ticket(
            tenant_id=tenant.pk,
            org_node_id=org_node_id,
            queue_id=payload.get("queue_id"),
            subject=subject,
            description=payload.get("description", ""),
            priority=payload.get("priority", "normal"),
            channel="api",
            requester_email=payload.get("requester_email", ""),
            requester_name=payload.get("requester_name", ""),
            custom_fields=payload.get("custom_fields"),
            tag_ids=tag_ids,
            actor_id=request.user.pk,
        )
        return Response(result, status=status.HTTP_201_CREATED)
    except ValueError as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)


# ---------------------------------------------------------------------------
# Email Ingestion
# ---------------------------------------------------------------------------

@extend_schema(
    description="Ingest an email into the helpdesk, routing to the queue whose email_address matches the 'to' field.",
    request={"application/json": {"type": "object", "required": ["to", "from_email"], "properties": {"to": {"type": "string", "format": "email"}, "from_email": {"type": "string", "format": "email"}, "from_name": {"type": "string"}, "subject": {"type": "string"}, "body": {"type": "string"}}}},
    responses={201: {"type": "object", "properties": {"ticket_id": {"type": "integer"}, "public_id": {"type": "string"}, "queue_id": {"type": "integer"}, "queue_name": {"type": "string"}}}},
)
@api_view(["POST"])
@permission_classes([IsAuthenticated])
def email_ingestion(request: Request) -> Response:
    """Ingest an email into the helpdesk, routing by queue email_address.

    Request body (JSON)::
        {
            "to": "support@company.com",     // matches Queue.email_address
            "from_email": "user@example.com",
            "from_name": "John Doe",
            "subject": "Need help with...",
            "body": "Full email body..."
        }
    """
    tenant = _tenant(request)
    org_node_id = _org_node_id(request)

    payload = request.data
    if not isinstance(payload, dict):
        return Response({"detail": "JSON object expected."}, status=status.HTTP_400_BAD_REQUEST)

    to_address = payload.get("to", "")
    from_email = payload.get("from_email", "")
    if not to_address or not from_email:
        return Response(
            {"detail": "to and from_email are required."},
            status=status.HTTP_400_BAD_REQUEST,
        )

    from simorgh.apps.helpdesk.email_ingestion import EmailIngestionService

    try:
        result = EmailIngestionService.ingest(
            tenant_id=tenant.pk,
            org_node_id=org_node_id,
            to_address=to_address,
            from_email=from_email,
            from_name=payload.get("from_name", ""),
            subject=payload.get("subject", "(no subject)"),
            body=payload.get("body", ""),
            actor_id=request.user.pk,
        )
        return Response(result, status=status.HTTP_201_CREATED)
    except ValueError as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)


# ---------------------------------------------------------------------------
# CSAT Survey
# ---------------------------------------------------------------------------

@extend_schema(
    description="Submit a satisfaction rating (1-5) for a solved ticket. Only the requester or a supervisor may submit.",
    request={"application/json": {"type": "object", "required": ["ticket_id", "rating"], "properties": {"ticket_id": {"type": "integer"}, "rating": {"type": "integer", "minimum": 1, "maximum": 5}, "comment": {"type": "string"}}}},
    responses={200: {"type": "object", "properties": {"ticket_id": {"type": "integer"}, "rating": {"type": "integer"}, "status": {"type": "string"}}}},
)
@api_view(["POST"])
@permission_classes([IsAuthenticated])
def csat_submit(request: Request) -> Response:
    """Submit a satisfaction rating for a solved ticket.

    Request body (JSON)::
        {
            "ticket_id": 123,
            "rating": 4,                    // 1-5
            "comment": "Great support!"     // optional
        }

    Only the ticket requester (or a supervisor) may submit a rating.
    """
    tenant = _tenant(request)
    ticket_id = request.data.get("ticket_id")
    rating = request.data.get("rating")
    comment = request.data.get("comment", "")

    if not ticket_id or rating is None:
        return Response(
            {"detail": "ticket_id and rating are required."},
            status=status.HTTP_400_BAD_REQUEST,
        )

    try:
        rating_int = int(rating)
        if rating_int < 1 or rating_int > 5:
            raise ValueError
    except (TypeError, ValueError):
        return Response(
            {"detail": "rating must be an integer between 1 and 5."},
            status=status.HTTP_400_BAD_REQUEST,
        )

    from simorgh.apps.helpdesk.models import Ticket, TicketStatus

    try:
        ticket = Ticket.objects.get(pk=ticket_id, tenant=tenant)
    except Ticket.DoesNotExist:
        return Response({"detail": "Ticket not found."}, status=status.HTTP_404_NOT_FOUND)

    if ticket.status not in (TicketStatus.SOLVED, TicketStatus.CLOSED):
        return Response(
            {"detail": "Can only submit CSAT for solved or closed tickets."},
            status=status.HTTP_400_BAD_REQUEST,
        )

    is_requester = ticket.requester_id == request.user.pk
    from simorgh.core.context import current_request_context
    ctx = current_request_context()
    is_supervisor = ctx.is_superuser or PERM_QUEUE_MANAGE in (ctx.permissions or set())

    if not is_requester and not is_supervisor:
        from rest_framework.exceptions import PermissionDenied
        raise PermissionDenied("Only the ticket requester or a supervisor may submit CSAT.")

    ticket.satisfaction_rating = rating_int
    ticket.satisfaction_comment = comment
    ticket.save(update_fields=["satisfaction_rating", "satisfaction_comment", "updated_at"])

    from simorgh.apps.events.bus import dispatch
    dispatch("helpdesk.csat.received", {
        "ticket_id": ticket.pk,
        "tenant_id": tenant.pk,
        "rating": rating_int,
        "comment": comment,
        "submitted_by": request.user.pk,
    })

    return Response({
        "ticket_id": ticket.pk,
        "rating": rating_int,
        "comment": comment,
        "status": "submitted",
    })

