# devices/views.py
from http.client import NOT_FOUND
from django.forms import ValidationError
from django.http import JsonResponse
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_control
from django.db.models import OuterRef, Subquery, F
from rest_framework import generics, permissions
from rest_framework.response import Response
from collections import defaultdict
from rest_framework import serializers
import sentry_sdk
from codesofy.master_details import ResultMode
from core.models import AppSettings
from django.conf import settings

from core.utils import _get_default_language, get_api_message
from cropmanagement.models import CropTranslation
from productcropmapping.models import ProductCropMapping
from scalemanagement.models import ScaleCropMapping
from .models import CustomerDevice, MyDeviceMoistureThreshold, shareDevice
from .serializers import CustomerDeviceCreateSerializer, CustomerDeviceReadSerializer, DeviceThresholdRowSerializer, MoistureThresholdBulkUpdateByCropSerializer, MoistureThresholdBulkUpdateByOrderSerializer, MoistureThresholdSingleUpdateByCropSerializer, MoistureThresholdSingleUpdateByOrderSerializer, ShareDeviceByEmailSerializer, ShareRecordSerializer
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import permissions, status
from django.db.models import Q
from django.db.models import ProtectedError
from django.db import transaction, IntegrityError
from rest_framework.exceptions import NotFound
from django.db.models import OuterRef, Subquery, F, Value, IntegerField, FloatField, CharField
from django.db.models.functions import Coalesce

class HasCustomerProfile(permissions.BasePermission):
    def has_permission(self, request, view):
        return getattr(request.user, "customer", None) is not None


class IsCustomerOwner(permissions.BasePermission):
    def has_object_permission(self, request, view, obj: CustomerDevice):
        customer = getattr(request.user, "customer", None)
        return customer is not None and obj.customer_id == customer.id

def _is_duplicate(e_codes) -> bool:
    """True if serializer error codes contain duplicate-serial or duplicate-label."""
    if not isinstance(e_codes, dict):
        return False
    sn = e_codes.get("serial_number", [])
    dl = e_codes.get("device_label", [])
    return ("duplicate-serial" in sn) or ("duplicate-label" in dl)


@method_decorator(cache_control(no_cache=True, must_revalidate=True, no_store=True), name="dispatch")
class CustomerDeviceListCreateView(APIView):
    """
    GET  -> list devices
    POST -> register device (UPSERT by serial_number)
            - if serial exists for this customer -> update
            - else -> create (first device becomes default)
    """
    permission_classes = [permissions.IsAuthenticated, HasCustomerProfile]

    def get(self, request):
        customer = request.user.customer

        qs = (
            CustomerDevice.objects
            .select_related("product", "language_id", "scale")
            .filter(customer=customer)
            .order_by("-created_at")
        )

        product_type = request.query_params.get("type")
        if product_type:
            qs = qs.filter(product__type=product_type)

        data = CustomerDeviceReadSerializer(qs, many=True, context={"request": request}).data
        return JsonResponse(
            {
                "success": True,
                "message": get_api_message("DEVICE_LIST", request),
                "code": "DEVICE_LIST",
                "data": data,
            },
            status=status.HTTP_200_OK,
        )

    def post(self, request):
        
        customer = request.user.customer

        # detect upsert target BEFORE validation/save
        incoming_serial = (request.data.get("serial_number") or "").strip().upper()
        incoming_product_id = request.data.get("product")
        existing_device = None
        if incoming_serial:
            existing_device = CustomerDevice.objects.filter(
                customer=customer,
                product=incoming_product_id,
                serial_number__iexact=incoming_serial
            ).first()

        ser = CustomerDeviceCreateSerializer(
            instance=existing_device,          
            data=request.data,
            context={"request": request},
            partial=False
        )

        try:
            ser.is_valid(raise_exception=True)
        except serializers.ValidationError as e:
            payload = e.detail if isinstance(e.detail, dict) else {"errors": {"non_field_errors": e.detail}}
            raw_code = payload.get("code", "VALIDATION_ERROR")

            # normalize code to string (DRF may wrap into list)
            if isinstance(raw_code, (list, tuple)) and raw_code:
                err_code = str(raw_code[0])
            else:
                err_code = str(raw_code)

            msg = get_api_message(err_code, request)
            errs = payload.get("errors", payload)

            # choose status based on error code
            http_status = status.HTTP_400_BAD_REQUEST
            if err_code in ("DEVICE_DUPLICATE_LABEL",):
                http_status = status.HTTP_409_CONFLICT

            return JsonResponse(
                {
                    "success": False,
                    "message": msg,
                    "code": err_code,    
                    "errors": errs,
                },
                status=http_status,
            )

        # save (create or update)
        device = ser.save()

        # If this is the FIRST device for the customer -> set default
        # (only when creating first device, not on updates)
        if not existing_device and CustomerDevice.objects.filter(customer=customer).count() == 1:
            with transaction.atomic():
                app_settings, _ = AppSettings.objects.get_or_create(customer_user=customer)
                app_settings.default_device = {
                    "device_id": device.id,
                    "device_name": device.device_label,
                    "product_name": device.product.name if device.product else None,
                    "serial": device.serial_number,
                    "setup_checksum": device.setup_checksum,
                    "language": device.language_id.language_id if device.language_id else None,
                    "scale_id": device.scale.scale_id if device.scale else None,
                    "firmware_version": device.firmware_version,
                }
                app_settings.save(update_fields=["default_device"])

        # response message for create/update
        if existing_device:
            resp_code = "DEVICE_UPDATED"
            resp_status = status.HTTP_200_OK
        else:
            resp_code = "DEVICE_CREATED"
            resp_status = status.HTTP_201_CREATED

        return JsonResponse(
            {
                "success": True,
                "message": get_api_message(resp_code, request),
                "code": resp_code,
                "data": {
                    "id": device.id,
                    "device_label": device.device_label,
                    "serial_number": device.serial_number,
                    "product_id": device.product_id,
                    "setup_checksum": device.setup_checksum,
                    "language_id": device.language_id.language_id if device.language_id else None,
                    "scale_id": device.scale.scale_id if device.scale else None,
                    "device_mode":device.device_mode,
                    "firmware_version": device.firmware_version,
                },
            },
            status=resp_status,
        )


@method_decorator(cache_control(no_cache=True, must_revalidate=True, no_store=True), name="dispatch")
class CustomerDeviceDetailAPIView(APIView):
    """
    GET    /api/v1/customer/devices/<id>/
    PUT    /api/v1/customer/devices/<id>/
    PATCH  /api/v1/customer/devices/<id>/
    DELETE /api/v1/customer/devices/<id>/
    """
    permission_classes = [permissions.IsAuthenticated, HasCustomerProfile, IsCustomerOwner]

    def get_object(self, id):
        customer = getattr(self.request.user, "customer", None)
        try:
            obj = CustomerDevice.objects.get(id=id, customer=customer)
            if not IsCustomerOwner().has_object_permission(self.request, self, obj):
                return None
            return obj
        except CustomerDevice.DoesNotExist:
            return None

    def get(self, request, id):
        obj = self.get_object(id)
        if not obj:
            return JsonResponse(
                {
                    "success": False,
                    "message": get_api_message("DEVICE_NOT_FOUND", request),
                    "code": "DEVICE_NOT_FOUND",
                },
                status=status.HTTP_404_NOT_FOUND,
            )
        data = CustomerDeviceReadSerializer(obj, context={"request": request}).data
        return JsonResponse(
            {
                "success": True,
                "message": get_api_message("DEVICE_FETCHED", request),
                "code": "DEVICE_FETCHED",
                "data": data,
            },
            status=status.HTTP_200_OK,
        )

    def put(self, request, id):
        return self._update(request, id, partial=False)

    def patch(self, request, id):
        return self._update(request, id, partial=True)

    def _update(self, request, id, partial):
        obj = self.get_object(id)
        if not obj:
            return JsonResponse(
                {
                    "success": False,
                    "message": get_api_message("DEVICE_NOT_FOUND", request),
                    "code": "DEVICE_NOT_FOUND",
                },
                status=status.HTTP_404_NOT_FOUND,
            )

        ser = CustomerDeviceCreateSerializer(obj, data=request.data, partial=partial, context={"request": request})
        try:
            ser.is_valid(raise_exception=True)
        except serializers.ValidationError as e:
            payload = e.detail if isinstance(e.detail, dict) else {"non_field_errors": e.detail}
            err_code_raw = payload.get("code", "VALIDATION_ERROR")

            if isinstance(err_code_raw, (list, tuple)) and err_code_raw:
                # e.g. [ErrorDetail('DEVICE_DUPLICATE_LABEL', code='invalid')]
                err_code = str(err_code_raw[0])
            else:
                err_code = str(err_code_raw)

            msg = get_api_message(err_code, request)
            errs = payload.get("errors", payload)

            return JsonResponse(
                {
                    "success": False,
                    "message": msg,
                    "code": "DEVICE_DUPLICATE",
                    "errors": errs,
                },
                status=status.HTTP_409_CONFLICT,
            )

        updated = ser.save()
        return JsonResponse(
            {
                "success": True,
                "message": get_api_message("DEVICE_UPDATED", request),
                "code": "DEVICE_UPDATED",
                "data": {
                    "id": updated.id,
                    "device_label": updated.device_label,
                    "serial_number": updated.serial_number,
                    "product_id": updated.product_id,
                    "setup_checksum": updated.setup_checksum,
                    "device_mode":updated.device_mode,
                    "firmware_version": updated.firmware_version,
                },
            },
            status=status.HTTP_200_OK,
        )

    def delete(self, request, id):
        obj = self.get_object(id)
        if not obj:
            return JsonResponse(
                {
                    "success": False,
                    "message": get_api_message("DEVICE_NOT_FOUND", request),
                    "code": "DEVICE_NOT_FOUND",
                },
                status=status.HTTP_404_NOT_FOUND,
            )

        customer = obj.customer
        device_id = obj.id

        try:
            with transaction.atomic():
                default_cleared = False
                app_settings = AppSettings.objects.select_for_update().filter(customer_user=customer).first()
                if app_settings:
                    d = app_settings.default_device or {}
                    current_default_id = d.get("device_id") or d.get("id")
                    if str(current_default_id) == str(device_id):
                        app_settings.default_device = {}
                        app_settings.save(update_fields=["default_device"])
                        default_cleared = True

                obj.delete()

            return JsonResponse(
                {
                    "success": True,
                    "message": get_api_message("DEVICE_DELETED", request),
                    "code": "DEVICE_DELETED",
                    "data": {"default_device_was_cleared": default_cleared},
                },
                status=status.HTTP_200_OK,
            )
        except Exception as e:
            return JsonResponse(
                {
                    "success": False,
                    "message": get_api_message("DEVICE_DELETE_FAILED", request),
                    "code": "DEVICE_DELETE_FAILED",
                    "errors": {"exception": [str(e)]},
                },
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


@method_decorator(cache_control(no_cache=True, must_revalidate=True, no_store=True), name="dispatch")
class ShareDeviceWithEmailView(APIView):
    permission_classes = [permissions.IsAuthenticated, HasCustomerProfile]

    def post(self, request):
        serializer = ShareDeviceByEmailSerializer(data=request.data, context={"request": request})
        serializer.is_valid(raise_exception=True)
        share_obj = serializer.save()

        payload = {
            "id": share_obj.id,
            "device_id": share_obj.device_id,
            "shared_with_id": share_obj.shared_with_id,
            "shared_with_email": getattr(share_obj.shared_with, "email", None),
            "shared_at": share_obj.shared_at,
            "message": (
                get_api_message("DEVICE_ALREADY_SHARED", request)
                if not getattr(serializer, "created", False)
                else get_api_message("DEVICE_SHARED_SUCCESS", request)
            ),
        }

        return Response(
            payload,
            status=status.HTTP_201_CREATED if getattr(serializer, "created", False) else status.HTTP_200_OK,
        )


@method_decorator(cache_control(no_cache=True, must_revalidate=True, no_store=True), name="dispatch")
class SharedDevicesListView(generics.ListAPIView):
    """
    GET /api/v1/customer/devices/shared/?scope=<shared-with-me|shared-to-others>
    - scope=shared-with-me     -> entries where I'm the recipient
    - scope=shared-to-others   -> entries where I'm the owner (device.customer)
    - (missing/anything else)  -> all entries involving me (received + sent)
    """
    permission_classes = [permissions.IsAuthenticated, HasCustomerProfile]
    serializer_class = ShareRecordSerializer

    def get_queryset(self):
        me = self.request.user.customer
        scope = (self.request.query_params.get("scope") or "").strip().lower()

        base = (
            shareDevice.objects
            .select_related("device__product", "device__customer", "shared_with")
            .order_by("-shared_at")
        )

        if scope == "shared-with-me":
            return base.filter(shared_with=me)

        if scope == "shared-to-others":
            return base.filter(device__customer=me)

        # default: all records involving me (both received and sent)
        return base.filter(Q(shared_with=me) | Q(device__customer=me))


@method_decorator(cache_control(no_cache=True, must_revalidate=True, no_store=True), name="dispatch")
class SetDefaultDeviceAPIView(APIView):
    permission_classes = [permissions.IsAuthenticated, HasCustomerProfile]

    def post(self, request, device_id: int):
        customer = request.user.customer
        testing_mode = request.data.get("testing_mode", None)

        device = (
            CustomerDevice.objects
            .filter(id=device_id, customer=customer)
            .select_related("product")
            .first()
        )

        if not device:
            return JsonResponse(
                {
                    "success": False,
                    "message": get_api_message("DEVICE_NOT_FOUND", request),
                    "code": "DEVICE_NOT_FOUND",
                    "errors": {
                        "device_id": ["No device with this id for the current customer."]
                    },
                },
                status=404,
            )

        app_settings, _ = AppSettings.objects.get_or_create(customer_user=customer)

        device_data = {
            "device_id": device.id,
            "device_name": device.device_label,
            "product_name": device.product.name if device.product else None,
            "serial": device.serial_number,
        }

        with transaction.atomic():
            app_settings.default_device = device_data

            if testing_mode is not None:
                if testing_mode in ResultMode.to_list():
                    app_settings.test_mode = testing_mode
                else:
                    app_settings.test_mode = ""
            else:
                app_settings.test_mode = ""

            app_settings.save(update_fields=["default_device", "test_mode", "updated_at"])

        return JsonResponse(
            {
                "success": True,
                "message": get_api_message("DEFAULT_DEVICE_SET", request),
                "code": "DEFAULT_DEVICE_SET",
                "data": {
                    "default_device": device_data,
                    "testing_mode": app_settings.test_mode,
                },
            },
            status=200,
        )
# ---------------------------------------------------------------------
# GET DEFAULT DEVICE (reads JSONField in AppSettings)
# ---------------------------------------------------------------------
@method_decorator(cache_control(no_cache=True, must_revalidate=True, no_store=True), name="dispatch")
class GetDefaultDeviceAPIView(APIView):
    permission_classes = [permissions.IsAuthenticated, HasCustomerProfile]

    def get(self, request):
        customer = request.user.customer

        app_settings = AppSettings.objects.filter(customer_user=customer).first()
        if not app_settings or not app_settings.default_device:
            return JsonResponse(
                {
                    "success": False,
                    "message": get_api_message("DEFAULT_DEVICE_NOT_SET", request),
                    "code": "DEFAULT_DEVICE_NOT_SET",
                    "data": {
                        "is_quick_test_device_set": False,
                        "default_device": None,
                        "testing_mode": app_settings.test_mode if app_settings else None,
                    },
                },
                status=status.HTTP_404_NOT_FOUND,
            )

        device_id = app_settings.default_device.get("device_id") or app_settings.default_device.get("id")
        device = (
            CustomerDevice.objects
            .filter(id=device_id, customer=customer)
            .select_related("product")
            .first()
        )

        if device:
            payload = CustomerDeviceReadSerializer(device, context={"request": request}).data
        else:
            payload = app_settings.default_device

        payload.update({
            "is_quick_test_device_set": True,
            "testing_mode": app_settings.test_mode,
        })

        return JsonResponse(
            {
                "success": True,
                "message": get_api_message("DEFAULT_DEVICE", request),
                "code": "DEFAULT_DEVICE",
                "data": payload,
            },
            status=status.HTTP_200_OK,
        )
@method_decorator(cache_control(no_cache=True, must_revalidate=True, no_store=True), name="dispatch")
class DeviceThresholdListByDeviceView(APIView):
    permission_classes = [permissions.IsAuthenticated, HasCustomerProfile]

    def get(self, request, device_id):
        customer = request.user.customer

        device = (
            CustomerDevice.objects
            .filter(pk=device_id, customer=customer)
            .select_related("scale")
            .first()
        )
        if not device:
            return JsonResponse(
                {
                    "success": False,
                    "message": get_api_message("DEVICE_NOT_FOUND", request),
                    "code": "DEVICE_NOT_FOUND",
                    "errors": {"device_id": [f"No device with id {device_id} for this customer."]},
                },
                status=404,
            )

        scale = device.scale
        if not scale:
            return JsonResponse(
                {
                    "success": False,
                    "message": get_api_message("SCALE_NOT_FOUND", request),
                    "code": "SCALE_NOT_FOUND",
                    "errors": {"scale_id": ["Device has no scale assigned."]},
                },
                status=400,
            )

        # ---- get language from Accept-Language header ----
        accept_language = request.headers.get("Accept-Language", "").strip()
        lang_code = None
        if accept_language:
            lang_code = accept_language.split(",")[0].split("-")[0].strip().lower()

        # ---- translation subquery based on header language ----
        translated_name_sq = CropTranslation.objects.none().values("name")[:1]
        if lang_code:
            translated_name_sq = (
                CropTranslation.objects
                .filter(
                    crop_id=OuterRef("crop_id"),
                    language__code__iexact=lang_code,   # change 'code' if your Language model uses another field
                )
                .values("name")[:1]
            )

        # ---- threshold subqueries ----
        threshold_id_sq = (
            MyDeviceMoistureThreshold.objects
            .filter(device_id=device_id, mapping_id=OuterRef("pk"))
            .values("id")[:1]
        )

        threshold_value_sq = (
            MyDeviceMoistureThreshold.objects
            .filter(device_id=device_id, mapping_id=OuterRef("pk"))
            .values("moisture_threshold")[:1]
        )

        qs = (
            ScaleCropMapping.objects
            .filter(scale=scale)
            .select_related("crop")
            .annotate(
                device_id_value=Value(device_id, output_field=IntegerField()),
                mapping_id=F("id"),
                crop_name=Coalesce(
                    Subquery(translated_name_sq, output_field=CharField()),
                    F("crop__name"),
                    output_field=CharField(),
                ),
                threshold_id=Subquery(threshold_id_sq, output_field=IntegerField()),
                moisture_threshold=Subquery(threshold_value_sq, output_field=FloatField()),
            )
            .order_by("scale_order_number")
        )

        data = DeviceThresholdRowSerializer(qs, many=True).data

        return JsonResponse(
            {
                "success": True,
                "message": get_api_message("DEVICE_THRESHOLD_LIST", request),
                "code": "DEVICE_THRESHOLD_LIST",
                "data": data,
            },
            status=200,
        )
@method_decorator(cache_control(no_cache=True, must_revalidate=True, no_store=True), name="dispatch")
class UpdateMoistureThresholdByOrderView(APIView):
    """
    PUT /api/v1/devices/<device_id>/thresholds/update-by-order/
    Body:
      { "scale_order_number": "3", "moisture_threshold": 15.5 }
      or list of them.
    """
    permission_classes = [permissions.IsAuthenticated, HasCustomerProfile]

    def put(self, request, device_id: int):
        customer = request.user.customer

        device = CustomerDevice.objects.filter(pk=device_id, customer=customer).select_related("scale").first()
        if not device:
            return JsonResponse(
                {
                    "success": False,
                    "message": get_api_message("DEVICE_NOT_FOUND", request),
                    "code": "DEVICE_NOT_FOUND",
                    "errors": {"device_id": [f"No device with id {device_id} for this customer."]},
                },
                status=404,
            )

        if not device.scale_id:
            return JsonResponse(
                {
                    "success": False,
                    "message": get_api_message("DEVICE_SCALE_NOT_SET", request),
                    "code": "DEVICE_SCALE_NOT_SET",
                    "errors": {"scale": ["This device has no scale set."]},
                },
                status=400,
            )

        data = request.data

        # ---- Bulk ----
        if isinstance(data, list):
            try:
                serializer = MoistureThresholdBulkUpdateByOrderSerializer(data=data, many=True)
                serializer.is_valid(raise_exception=True)

                result = serializer.update_for_device(device, serializer.validated_data)

                return JsonResponse(
                    {
                        "success": True,
                        "message": get_api_message("MOISTURE_THRESHOLD_BULK_UPDATED", request),
                        "code": "MOISTURE_THRESHOLD_BULK_UPDATED",
                        "data": {"device_id": device_id, **result},
                    },
                    status=200,
                )
            except serializers.ValidationError as e:
                detail = e.detail if isinstance(e.detail, dict) else {"non_field_errors": e.detail}
                return JsonResponse(
                    {
                        "success": False,
                        "message": get_api_message("VALIDATION_ERROR", request),
                        "code": "VALIDATION_ERROR",
                        "errors": detail,
                    },
                    status=400,
                )

        # ---- Single ----
        if isinstance(data, dict):
            try:
                serializer = MoistureThresholdSingleUpdateByOrderSerializer(data=data)
                serializer.is_valid(raise_exception=True)

                order_no = serializer.validated_data["scale_order_number"].strip()
                new_val = serializer.validated_data["moisture_threshold"]

                obj = (
                    MyDeviceMoistureThreshold.objects
                    .select_related("mapping")
                    .filter(
                        device=device,
                        mapping__scale=device.scale,
                        mapping__scale_order_number__iexact=order_no,
                    )
                    .first()
                )
                if not obj:
                    return JsonResponse(
                        {
                            "success": False,
                            "message": get_api_message("MOISTURE_THRESHOLD_NOT_FOUND", request),
                            "code": "MOISTURE_THRESHOLD_NOT_FOUND",
                            "errors": {"scale_order_number": [f"No threshold for order '{order_no}' on device {device_id}."]},
                        },
                        status=404,
                    )

                obj.moisture_threshold = new_val
                obj.save(update_fields=["moisture_threshold"])

                return JsonResponse(
                    {
                        "success": True,
                        "message": get_api_message("MOISTURE_THRESHOLD_UPDATED", request),
                        "code": "MOISTURE_THRESHOLD_UPDATED",
                        "data": {
                            "device_id": device_id,
                            "scale_order_number": order_no,
                            "moisture_threshold": float(new_val),
                        },
                    },
                    status=200,
                )
            except serializers.ValidationError as e:
                detail = e.detail if isinstance(e.detail, dict) else {"non_field_errors": e.detail}
                return JsonResponse(
                    {
                        "success": False,
                        "message": get_api_message("VALIDATION_ERROR", request),
                        "code": "VALIDATION_ERROR",
                        "errors": detail,
                    },
                    status=400,
                )

        return JsonResponse(
            {
                "success": False,
                "message": get_api_message("INVALID_PAYLOAD", request),
                "code": "INVALID_PAYLOAD",
                "errors": {"detail": ["Body must be a single object or a list of objects."]},
            },
            status=400,
        )

@method_decorator(cache_control(no_cache=True, must_revalidate=True, no_store=True), name="dispatch")
class UpdateMoistureThresholdByCropView(APIView):
    """
    PUT /api/v1/devices/<device_id>/thresholds/update-by-crop/
    Body can be single object or list of objects:
      { "crop_name": "Wheat", "moisture_threshold": 15.5 }
      [
        {"crop_name": "crop3", "moisture_threshold": 15.5},
        {"crop_name": "crop4", "moisture_threshold": 16.0}
      ]
    """
    permission_classes = [permissions.IsAuthenticated, HasCustomerProfile]

    def put(self, request, device_id: int):
        customer = request.user.customer

        device = CustomerDevice.objects.filter(pk=device_id, customer=customer).first()
        if not device:
            return JsonResponse(
                {
                    "success": False,
                    "message": get_api_message("DEVICE_NOT_FOUND", request),
                    "code": "DEVICE_NOT_FOUND",
                    "errors": {"device_id": [f"No device with id {device_id} for this customer."]},
                },
                status=404,
            )

        data = request.data

        # ---- Bulk ----
        if isinstance(data, list):
            try:
                serializer = MoistureThresholdBulkUpdateByCropSerializer(data=data, many=True)
                serializer.is_valid(raise_exception=True)
                result = serializer.update_for_device(device_id, serializer.validated_data)  # your custom API
                return JsonResponse(
                    {
                        "success": True,
                        "message": get_api_message("MOISTURE_THRESHOLD_BULK_UPDATED", request),
                        "code": "MOISTURE_THRESHOLD_BULK_UPDATED",
                        "data": {"device_id": device_id, **result},
                    },
                    status=200,
                )
            except serializers.ValidationError as e:
                detail = e.detail if isinstance(e.detail, dict) else {"non_field_errors": e.detail}
                return JsonResponse(
                    {
                        "success": False,
                        "message": get_api_message("VALIDATION_ERROR", request),
                        "code": "VALIDATION_ERROR",
                        "errors": detail,
                    },
                    status=400,
                )

        # ---- Single ----
        if isinstance(data, dict):
            try:
                serializer = MoistureThresholdSingleUpdateByCropSerializer(data=data)
                serializer.is_valid(raise_exception=True)

                crop_name = serializer.validated_data["crop_name"].strip()
                new_val = serializer.validated_data["moisture_threshold"]

                obj = (
                    MyDeviceMoistureThreshold.objects
                    .select_related("mapping__crop")
                    .filter(device_id=device_id, mapping__crop__name__iexact=crop_name)
                    .first()
                )
                if not obj:
                    return JsonResponse(
                        {
                            "success": False,
                            "message": get_api_message("MOISTURE_THRESHOLD_NOT_FOUND", request),
                            "code": "MOISTURE_THRESHOLD_NOT_FOUND",
                            "errors": {"crop_name": [f"No threshold for '{crop_name}' on device {device_id}."]},
                        },
                        status=404,
                    )

                obj.moisture_threshold = new_val
                obj.save(update_fields=["moisture_threshold"])

                return JsonResponse(
                    {
                        "success": True,
                        "message": get_api_message("MOISTURE_THRESHOLD_UPDATED", request),
                        "code": "MOISTURE_THRESHOLD_UPDATED",
                        "data": {
                            "device_id": device_id,
                            "crop_name": crop_name,
                            "moisture_threshold": float(new_val),
                        },
                    },
                    status=200,
                )
            except serializers.ValidationError as e:
                detail = e.detail if isinstance(e.detail, dict) else {"non_field_errors": e.detail}
                return JsonResponse(
                    {
                        "success": False,
                        "message": get_api_message("VALIDATION_ERROR", request),
                        "code": "VALIDATION_ERROR",
                        "errors": detail,
                    },
                    status=400,
                )

        # ---- Invalid payload type ----
        return JsonResponse(
            {
                "success": False,
                "message": get_api_message("INVALID_PAYLOAD", request),
                "code": "INVALID_PAYLOAD",
                "errors": {"detail": ["Body must be a single object or a list of objects."]},
            },
            status=400,
        )



@method_decorator(cache_control(no_cache=True, must_revalidate=True, no_store=True), name="dispatch")
class GetMoistureThresholdAPIView(APIView):
    permission_classes = [permissions.IsAuthenticated, HasCustomerProfile]

    def get(self, request):
        customer = request.user.customer
        device_id = request.query_params.get("device_id")
        scale_order_number = request.query_params.get("scale_order_number")

        if not device_id:
            return JsonResponse(
                {
                    "success": False,
                    "message": get_api_message("MISSING_DEVICE_ID", request),
                    "code": "MISSING_DEVICE_ID",
                    "errors": {"device_id": ["This query parameter is required."]},
                },
                status=400,
            )

        if not scale_order_number:
            return JsonResponse(
                {
                    "success": False,
                    "message": get_api_message("MISSING_SCALE_ORDER_NUMBER", request),
                    "code": "MISSING_SCALE_ORDER_NUMBER",
                    "errors": {"scale_order_number": ["This query parameter is required."]},
                },
                status=400,
            )

        device = (
            CustomerDevice.objects
            .select_related("scale", "language_id")
            .filter(id=device_id, customer=customer)
            .first()
        )
        if not device:
            return JsonResponse(
                {
                    "success": False,
                    "message": get_api_message("DEVICE_NOT_FOUND", request),
                    "code": "DEVICE_NOT_FOUND",
                    "errors": {"device_id": [f"No device with id {device_id} for this customer."]},
                },
                status=404,
            )

        if not device.scale:
            return JsonResponse(
                {
                    "success": False,
                    "message": get_api_message("DEVICE_SCALE_NOT_SET", request),
                    "code": "DEVICE_SCALE_NOT_SET",
                    "errors": {"scale": ["This device has no scale set."]},
                },
                status=400,
            )

        # -------------------------
        # Language resolution
        # -------------------------
        primary_lang = device.language_id
        fallback_lang = _get_default_language()
        print(primary_lang)
        print(fallback_lang)
        if not primary_lang and not fallback_lang:
            return JsonResponse(
                {
                    "success": False,
                    "message": get_api_message("LANGUAGE_NOT_FOUND", request),
                    "code": "LANGUAGE_NOT_FOUND",
                    "errors": {"language": ["No language available for translation."]},
                },
                status=400,
            )

        # ---- subqueries ----
        primary_translation_sq = (
            CropTranslation.objects
            .filter(
                crop_id=OuterRef("crop_id"),
                language=primary_lang,
            )
            .values("name")[:1]
        )

        fallback_translation_sq = (
            CropTranslation.objects
            .filter(
                crop_id=OuterRef("crop_id"),
                language=fallback_lang,
            )
            .values("name")[:1]
        )

        mapping = (
            ScaleCropMapping.objects
            .select_related("crop")
            .filter(
                scale=device.scale,
                scale_order_number=scale_order_number,
            )
            .annotate(
                crop_name_primary=Subquery(primary_translation_sq, output_field=CharField()),
                crop_name_fallback=Subquery(fallback_translation_sq, output_field=CharField()),
            )
            .first()
        )

        if not mapping:
            return JsonResponse(
                {
                    "success": False,
                    "message": get_api_message("MAPPING_NOT_FOUND", request),
                    "code": "MAPPING_NOT_FOUND",
                    "errors": {
                        "detail": [f"No mapping for scale_order_number '{scale_order_number}' on this device scale."]
                    },
                },
                status=404,
            )

        crop_name = mapping.crop_name_primary or mapping.crop_name_fallback

        if not crop_name:
            return JsonResponse(
                {
                    "success": False,
                    "message": get_api_message("CROP_TRANSLATION_NOT_FOUND", request),
                    "code": "CROP_TRANSLATION_NOT_FOUND",
                    "errors": {
                        "detail": [f"No crop translation found for this crop in any language."]
                    },
                },
                status=404,
            )

        device_thr = (
            MyDeviceMoistureThreshold.objects
            .filter(device=device, mapping=mapping)
            .values_list("moisture_threshold", flat=True)
            .first()
        )

        return JsonResponse(
            {
                "success": True,
                "message": get_api_message("MOISTURE_THRESHOLD_DETAIL", request),
                "code": "MOISTURE_THRESHOLD_DETAIL",
                "data": {
                    "crop_name": crop_name,
                    "my_device_threshold": float(device_thr) if device_thr is not None else None,
                },
            },
            status=200,
        )