"""
`/api/tasks/` — the reference slice, and the shape every feature repeats.

List, create, patch, delete, `bulk` and `sample` all come from the base and the
two mixins. What is specific to Tasks is one action:

**`POST /api/tasks/{id}/log-time/` is an atomic increment, not a PATCH.** The
Focus page logs minutes against a task, the client has nowhere to put an
increment and would send a total, and two sessions ending seconds apart — a
laptop waking with a queued write, or a second device — then read the same
"before" value, so the later write silently discards the earlier one's minutes.
That is a lost measurement on the one page whose entire job is measuring.
`actual_minutes` is therefore read-only on the serializer and `F()` does the
arithmetic in the database.
"""

from django.db.models import F, Value
from django.db.models.functions import Greatest
from django.utils import timezone
from drf_spectacular.utils import extend_schema
from rest_framework.decorators import action
from rest_framework.response import Response

from ..mixins import BulkReplaceMixin, PageScopedViewSet, SampleMixin
from ..models import Task
from ..samples.tasks import sample_tasks
from ..serializers.tasks import LogTimeSerializer, TaskSerializer


class TaskViewSet(BulkReplaceMixin, SampleMixin, PageScopedViewSet):
    queryset = Task.objects.all()
    serializer_class = TaskSerializer
    bulk_key = 'tasks'
    sample_factory = staticmethod(sample_tasks)

    @extend_schema(
        summary='Add minutes to a task\'s logged time.',
        request=LogTimeSerializer,
        responses={200: TaskSerializer},
    )
    @action(detail=True, methods=['post'], url_path='log-time')
    def log_time(self, request, pk=None):
        task = self.get_object()
        body = LogTimeSerializer(data=request.data)
        body.is_valid(raise_exception=True)

        # `Greatest` rather than a check-then-write: a correction that takes off
        # more minutes than are there is a race away from a negative total, and
        # a task that has been worked on for minus twenty minutes is a number no
        # screen in the app can render honestly. `auto_now` does not fire on a
        # queryset update, so `updated_at` is set by hand or it goes stale.
        Task.objects.filter(pk=task.pk).update(
            actual_minutes=Greatest(F('actual_minutes') + body.validated_data['minutes'], Value(0)),
            updated_at=timezone.now(),
        )
        task.refresh_from_db()
        return Response(self.get_serializer(task).data)
