from datetime import timedelta

from django.db.models import Count, Q, Sum
from django.db.models.functions import TruncDate
from django.utils import timezone
from rest_framework import generics, status
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView

from accounts.permissions import IsAdmin

from .models import TruckMovement
from .serializers import (
    TruckEntrySerializer,
    TruckExitSerializer,
    TruckMovementSerializer,
    TruckUpdateSerializer,
)


class TruckEntryCreateView(generics.CreateAPIView):
    """POST /api/trucks/entry/ - registers a new truck entering the park."""
    queryset = TruckMovement.objects.all()
    serializer_class = TruckEntrySerializer
    permission_classes = [IsAuthenticated]


class TruckExitView(APIView):
    """POST /api/trucks/exit/ - looks up a truck by registration_number and records its exit."""
    permission_classes = [IsAuthenticated]

    def post(self, request):
        serializer = TruckExitSerializer(data=request.data, context={'request': request})
        serializer.is_valid(raise_exception=True)
        movement = serializer.save()
        return Response(TruckMovementSerializer(movement).data, status=status.HTTP_200_OK)


class TruckMovementListView(generics.ListAPIView):
    """GET /api/trucks/ - paginated list, filterable by ?status=, ?q=, ?from=, ?to=
    (from/to match entry_datetime's date, inclusive, format YYYY-MM-DD)."""
    serializer_class = TruckMovementSerializer
    permission_classes = [IsAuthenticated]

    def get_queryset(self):
        queryset = TruckMovement.objects.all()
        params = self.request.query_params

        status_param = params.get('status')
        if status_param:
            queryset = queryset.filter(status=status_param)

        q = params.get('q', '').strip()
        if q:
            queryset = queryset.filter(
                Q(truck_number__icontains=q)
                | Q(registration_number__icontains=q)
                | Q(t_number__icontains=q)
            )

        date_from = params.get('from')
        date_to = params.get('to')
        if date_from:
            queryset = queryset.filter(entry_datetime__date__gte=date_from)
        if date_to:
            queryset = queryset.filter(entry_datetime__date__lte=date_to)

        return queryset


class TruckMovementSearchView(generics.ListAPIView):
    """GET /api/trucks/search/?q=... - search by truck number, registration number or T number."""
    serializer_class = TruckMovementSerializer
    permission_classes = [IsAuthenticated]

    def get_queryset(self):
        q = self.request.query_params.get('q', '').strip()
        if not q:
            return TruckMovement.objects.none()
        return TruckMovement.objects.filter(
            Q(truck_number__icontains=q)
            | Q(registration_number__icontains=q)
            | Q(t_number__icontains=q)
        )


class TruckMovementDetailView(generics.RetrieveUpdateDestroyAPIView):
    """GET /api/trucks/<id>/ - detail (any authenticated user).
    PATCH/PUT /api/trucks/<id>/ - admin only, edits the movement.
    DELETE /api/trucks/<id>/ - admin only.
    """
    queryset = TruckMovement.objects.all()

    def get_serializer_class(self):
        if self.request.method in ('PUT', 'PATCH'):
            return TruckUpdateSerializer
        return TruckMovementSerializer

    def get_permissions(self):
        if self.request.method in ('DELETE', 'PUT', 'PATCH'):
            return [IsAdmin()]
        return [IsAuthenticated()]

    def update(self, request, *args, **kwargs):
        super().update(request, *args, **kwargs)
        # Answer with the read serializer so the client gets current_penalty back.
        instance = self.get_object()
        return Response(TruckMovementSerializer(instance).data)


class DashboardStatsView(APIView):
    """GET /api/dashboard/stats/ - park occupancy and penalty totals."""
    permission_classes = [IsAuthenticated]

    def get(self, request):
        now = timezone.localtime()
        today = now.date()

        in_park_count = TruckMovement.objects.filter(status=TruckMovement.Status.IN_PARK).count()
        entries_today = TruckMovement.objects.filter(entry_datetime__date=today).count()
        exits_today = TruckMovement.objects.filter(exit_datetime__date=today).count()

        penalties_today = TruckMovement.objects.filter(
            exit_datetime__date=today
        ).aggregate(total=Sum('penalty_amount'))['total'] or 0

        penalties_month = TruckMovement.objects.filter(
            exit_datetime__year=today.year, exit_datetime__month=today.month
        ).aggregate(total=Sum('penalty_amount'))['total'] or 0

        # Trucks still in the park keep accruing: their penalty is not written to
        # penalty_amount until they exit, so it is computed live here.
        pending_penalties = sum(
            m.calculate_penalty(at=now)
            for m in TruckMovement.objects.filter(status=TruckMovement.Status.IN_PARK)
        )

        return Response({
            'trucks_in_park': in_park_count,
            'entries_today': entries_today,
            'exits_today': exits_today,
            'penalties_today': penalties_today,
            'penalties_month': penalties_month,
            'pending_penalties': pending_penalties,
            'penalties_today_total': penalties_today + pending_penalties,
            'penalties_month_total': penalties_month + pending_penalties,
            'free_hours': TruckMovement.FREE_HOURS,
            'free_days': TruckMovement.FREE_DAYS,
            'penalty_per_hour': TruckMovement.PENALTY_PER_HOUR,
        })


class DashboardTrendsView(APIView):
    """GET /api/dashboard/trends/?days=30 - one row per day for the last N days
    (default 30, max 90), each with entries/exits counts and penalty total."""
    permission_classes = [IsAuthenticated]

    def get(self, request):
        days = max(1, min(int(request.query_params.get('days', 30)), 90))
        today = timezone.localtime().date()
        start = today - timedelta(days=days - 1)

        entries_by_day = {
            row['day']: row['count']
            for row in (
                TruckMovement.objects.filter(entry_datetime__date__gte=start)
                .annotate(day=TruncDate('entry_datetime'))
                .values('day')
                .annotate(count=Count('id'))
            )
        }
        exits_rows = (
            TruckMovement.objects.filter(exit_datetime__date__gte=start)
            .annotate(day=TruncDate('exit_datetime'))
            .values('day')
            .annotate(count=Count('id'), penalties=Sum('penalty_amount'))
        )
        exits_by_day = {row['day']: row['count'] for row in exits_rows}
        penalties_by_day = {row['day']: row['penalties'] or 0 for row in exits_rows}

        series = [
            {
                'date': (start + timedelta(days=i)).isoformat(),
                'entries': entries_by_day.get(start + timedelta(days=i), 0),
                'exits': exits_by_day.get(start + timedelta(days=i), 0),
                'penalties': penalties_by_day.get(start + timedelta(days=i), 0),
            }
            for i in range(days)
        ]
        return Response(series)
