import json

from django.db.models import Avg, Max, Min
from django.shortcuts import get_object_or_404, render
from django.utils import timezone
from rest_framework import generics, status
from rest_framework.response import Response
from rest_framework.views import APIView

from .gps_quality import (
    MIN_STRONG_SATELLITES,
    classify_points,
    point_quality,
)
from .models import Device, LocationPoint
from .serializers import (
    DeviceSerializer,
    LocationIngestSerializer,
    LocationPointSerializer,
)


class LocationIngestView(APIView):
    """POST endpoint for ESP32 / GSM modules to push GPS data."""

    authentication_classes = []
    permission_classes = []

    def post(self, request):
        serializer = LocationIngestSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        location = serializer.save()
        return Response(
            {
                "status": "ok",
                "location_id": location.id,
                "device_id": location.device.device_id,
                "recorded_at": location.recorded_at,
            },
            status=status.HTTP_201_CREATED,
        )


class DeviceListView(generics.ListAPIView):
    queryset = Device.objects.all()
    serializer_class = DeviceSerializer


class DeviceDetailView(generics.RetrieveAPIView):
    lookup_field = "device_id"
    queryset = Device.objects.all()
    serializer_class = DeviceSerializer


class DeviceLocationHistoryView(generics.ListAPIView):
    serializer_class = LocationPointSerializer

    def get_queryset(self):
        device = get_object_or_404(Device, device_id=self.kwargs["device_id"])
        queryset = device.locations.all()
        limit = self.request.query_params.get("limit")
        if limit and limit.isdigit():
            queryset = queryset[: int(limit)]
        return queryset


class DeviceLatestLocationView(APIView):
    def get(self, request, device_id):
        device = get_object_or_404(Device, device_id=device_id)
        location = device.latest_location
        if not location:
            return Response({"detail": "No locations recorded yet."}, status=404)
        return Response(LocationPointSerializer(location).data)


def dashboard(request):
    devices = Device.objects.prefetch_related("locations").all()
    device_rows = []
    for device in devices:
        latest = device.latest_location
        device_rows.append(
            {
                "device": device,
                "latest": latest,
                "location_count": device.locations.count(),
            }
        )
    return render(request, "tracking/dashboard.html", {"device_rows": device_rows})


def device_detail(request, device_id):
    device = get_object_or_404(Device, device_id=device_id)
    limit = request.GET.get("limit", "100")
    try:
        history_limit = max(1, min(int(limit), 500))
    except ValueError:
        history_limit = 100

    history = list(device.locations.all()[:history_limit])
    mappable_history = [
        point for point in history if point.latitude and point.longitude
    ]
    classifications = classify_points(history)
    strong_count = sum(
        1 for p in mappable_history if classifications.get(p.id) == "strong"
    )
    weak_count = sum(
        1 for p in mappable_history if classifications.get(p.id) == "weak"
    )
    jitter_count = sum(
        1 for p in mappable_history if classifications.get(p.id) == "jitter"
    )

    stats = device.locations.filter(fix_valid=True).aggregate(
        max_speed=Max("speed"),
        avg_speed=Avg("speed"),
        max_altitude=Max("altitude"),
        min_altitude=Min("altitude"),
    )

    map_points = [
        {
            "lat": float(point.latitude),
            "lng": float(point.longitude),
            "time": timezone.localtime(point.recorded_at).strftime("%Y-%m-%d %H:%M:%S"),
            "speed": point.speed,
            "altitude": point.altitude,
            "satellites": point.satellites or 0,
            "quality": point_quality(point, classifications=classifications),
        }
        for point in reversed(mappable_history)
    ]

    context = {
        "device": device,
        "latest": device.latest_location,
        "history": history,
        "map_points_json": json.dumps(map_points),
        "stats": stats,
        "history_limit": history_limit,
        "strong_count": strong_count,
        "weak_count": weak_count,
        "jitter_count": jitter_count,
        "min_strong_satellites": MIN_STRONG_SATELLITES,
        "classifications": classifications,
    }
    return render(request, "tracking/device_detail.html", context)
