# devices/serializers.py
from django.db import IntegrityError, transaction
from rest_framework import serializers

from core.models import Language
from core.utils import _get_default_language
from customermanagement.models import Customer
from products.serializers import ProductSerializer
from django.db.models.functions import Lower

from scalemanagement.models import Scale, ScaleCropMapping
from .models import CustomerDevice, MyDeviceMoistureThreshold, shareDevice
from products.models import Product
from constants.general_const import ActiveStatus


class CustomerDeviceReadSerializer(serializers.ModelSerializer):
    product = serializers.SerializerMethodField()
    language_id = serializers.CharField(required=False, allow_blank=True, allow_null=True ,source="language_id.language_id",)
    scale_id =  serializers.CharField(required=False, allow_blank=True, allow_null=True,source="scale.scale_id")

    class Meta:
        model = CustomerDevice
        fields = [
            "id",
            "product",
            "serial_number",
            "device_label",
            "created_at",
            "temperature_unit",
            "density_unit",
            "setup_checksum",
            "language_id",
            "scale_id",
            "device_mode", 
            "firmware_version",
        ]

    def get_product(self, obj):
        return ProductSerializer(obj.product).data if obj.product else None
    
class CustomerDeviceCreateSerializer(serializers.ModelSerializer):
    #  allow frontend to send OR fallback to product defaults
    temperature_unit = serializers.CharField(required=False, allow_blank=True, allow_null=True)
    density_unit = serializers.CharField(required=False, allow_blank=True, allow_null=True)
    language_id = serializers.CharField(required=False, allow_blank=True, allow_null=True)
    scale_id =  serializers.CharField(required=False, allow_blank=True, allow_null=True)

    class Meta:
        model = CustomerDevice
        fields = [
            "id",
            "product",
            "serial_number",
            "device_label",
            "temperature_unit",
            "density_unit",
            "setup_checksum",
            "language_id",
            "scale_id",
            "device_mode", 
            "firmware_version",
        ]
        extra_kwargs = {"serial_number": {"validators": []}}
        validators = []

    def validate(self, attrs):
        request = self.context["request"]
        customer = getattr(request.user, "customer", None)
        if customer is None:
            raise serializers.ValidationError({"code": "NO_CUSTOMER_PROFILE"})

        product = attrs.get("product")
        if not isinstance(product, Product):
            raise serializers.ValidationError({"code": "INVALID_PRODUCT"})
        
        firmware_version = attrs.get("firmware_version", None)
        if isinstance(firmware_version, str):
            firmware_version = firmware_version.strip() or None

        attrs["firmware_version"] = firmware_version
        
        # --- normal validations ---
        serial = (attrs.get("serial_number") or "").strip().upper()
        label = (attrs.get("device_label") or "").strip()
        # Only normalize/update device_mode if client sent it
        if "device_mode" in attrs:
            device_mode = attrs.get("device_mode")
            if isinstance(device_mode, str):
                device_mode = device_mode.strip() or None

            # If you want: treat "" as "don't change" (recommended for PATCH)
            if device_mode is None and self.instance is not None:
                attrs.pop("device_mode", None)
            else:
                attrs["device_mode"] = device_mode
                        
        # ---- SCALE: validate FK ----
        incoming_scale_id = attrs.get("scale_id")
        incoming_scale_id = (incoming_scale_id.strip() if isinstance(incoming_scale_id, str) else "") or ""

        if not incoming_scale_id:
            raise serializers.ValidationError({"code": "SCALE_ID_REQUIRED", "errors": {"scale_id": ["required"]}})

        scale_obj = Scale.objects.filter(scale_id=incoming_scale_id).first()
        if not scale_obj:
            raise serializers.ValidationError({
                "code": "SCALE_NOT_FOUND",
                "errors": {"scale_id": [f"Invalid scale_id '{incoming_scale_id}'"]}
            })

        # store the actual FK object under the model field name
        attrs["scale"] = scale_obj
        attrs.pop("scale_id", None)

        if not serial:
            raise serializers.ValidationError({"code": "SERIAL_REQUIRED", "errors": {"serial_number": ["required"]}})
        if not label:
            raise serializers.ValidationError({"code": "DEVICE_LABEL_REQUIRED", "errors": {"device_label": ["required"]}})

            
        incoming_lang_id = attrs.get("language_id")
        incoming_lang_id = (incoming_lang_id.strip() if isinstance(incoming_lang_id, str) else "") or ""

        if incoming_lang_id:
            # frontend sends Language.language_id (NOT pk)
            lang_obj = Language.objects.filter(language_id=incoming_lang_id).first()
            if not lang_obj:
                raise serializers.ValidationError({
                    "code": "LANGUAGE_NOT_FOUND",
                    "errors": {"language_id": [f"Invalid language_id '{incoming_lang_id}'"]}
                })
            attrs["language_id"] = lang_obj
        else:
            # default by language code (en)
            default_code = _get_default_language() or "en"
            lang_obj = Language.objects.filter(code=default_code).first() or Language.objects.first()
            if not lang_obj:
                raise serializers.ValidationError({"code": "LANGUAGE_NOT_FOUND"})
            attrs["language_id"] = lang_obj


        checksum = attrs.get("setup_checksum", None)
        if isinstance(checksum, str):
            attrs["setup_checksum"] = checksum.strip() or None

        #  temperature_unit / density_unit:
        # if frontend sends blank -> treat as not provided
        tu = attrs.get("temperature_unit", None)
        du = attrs.get("density_unit", None)
        tu = (tu.strip() if isinstance(tu, str) else tu) or None
        du = (du.strip() if isinstance(du, str) else du) or None

        #  if not provided, fallback to product defaults
        if tu is None:
            tu = getattr(product, "default_temperature", None)
        if du is None:
            du = getattr(product, "default_density", None)


        attrs["temperature_unit"] = tu
        attrs["density_unit"] = du

        # --- duplicate checks ---
        serial_qs = CustomerDevice.objects.filter(customer=customer, product=product)
        if self.instance:
            serial_qs = serial_qs.exclude(pk=self.instance.pk)

        if serial_qs.annotate(sn=Lower("serial_number")).filter(sn=serial.lower()).exists():
            raise serializers.ValidationError({
                "code": "DEVICE_DUPLICATE_SERIAL_NUMBER",
                "errors": {"serial_number": ["duplicate"]}
            })

        label_qs = CustomerDevice.objects.filter(customer=customer)
        if self.instance:
            label_qs = label_qs.exclude(pk=self.instance.pk)

        if label_qs.annotate(lbl=Lower("device_label")).filter(lbl=label.lower()).exists():
            raise serializers.ValidationError({
                "code": "DEVICE_DUPLICATE_LABEL",
                "errors": {"device_label": ["duplicate"]}
            })

        attrs["serial_number"] = serial
        attrs["device_label"] = label
        return attrs

    def create(self, validated_data):
        customer = self.context["request"].user.customer
        serial = validated_data["serial_number"]
        product = validated_data["product"]

        existing = CustomerDevice.objects.filter(
            customer=customer,
            product=product,
            serial_number__iexact=serial
        ).first()

        if existing:
            new_label = validated_data.get("device_label")
            if new_label:
                clash = (
                    CustomerDevice.objects
                    .filter(customer=customer)
                    .exclude(pk=existing.pk)
                    .annotate(lbl=Lower("device_label"))
                    .filter(lbl=new_label.lower())
                    .exists()
                )
                if clash:
                    raise serializers.ValidationError({
                        "code": "DEVICE_DUPLICATE_LABEL",
                        "errors": {"device_label": ["duplicate"]}
                    })

            for field in [
                "product",
                "device_label",
                "temperature_unit",
                "density_unit",
                "setup_checksum",
                "language_id",
                "scale",
                "device_mode",  
                "firmware_version",
            ]:
                if field in validated_data:
                    setattr(existing, field, validated_data[field])

            existing.save()
            return existing

        return CustomerDevice.objects.create(customer=customer, **validated_data)

class ShareDeviceByEmailSerializer(serializers.Serializer):
    device_id = serializers.IntegerField()
    email = serializers.EmailField()

    def validate(self, attrs):
        request = self.context["request"]
        customer = getattr(request.user, "customer", None)
        if customer is None:
            raise serializers.ValidationError("No customer profile is linked to this user.")

        # Must be the owner of the device
        try:
            device = CustomerDevice.objects.get(id=attrs["device_id"], customer=customer)
        except CustomerDevice.DoesNotExist:
            raise serializers.ValidationError({"device_id": "Device not found or does not belong to you."})

        # Recipient must exist
        try:
            recipient = Customer.objects.get(email=attrs["email"])
        except Customer.DoesNotExist:
            raise serializers.ValidationError({"email": "No customer found with this email."})

        # Prevent sharing with self
        if recipient.id == customer.id:
            raise serializers.ValidationError({"email": "You cannot share a device with yourself."})

        attrs["device"] = device
        attrs["recipient"] = recipient
        return attrs

    def create(self, validated_data):
        device = validated_data["device"]
        recipient = validated_data["recipient"]
        obj, created = shareDevice.objects.get_or_create(
            device=device,
            shared_with=recipient,
        )
        # Store flag so the view can decide 200 vs 201
        self.created = created
        return obj

class ShareRecordSerializer(serializers.ModelSerializer):
    device = serializers.SerializerMethodField()
    other_party = serializers.SerializerMethodField()
    role = serializers.SerializerMethodField()  # "received" (shared-with-me) or "sent" (shared-to-others)

    class Meta:
        model = shareDevice
        fields = ["id", "shared_at", "role", "device", "other_party"]

    def get_device(self, obj):
        d = obj.device
        p = getattr(d, "product", None)
        return {
            "id": d.id,
            "device_label": d.device_label,
            "serial_number": d.serial_number,
            "temperature_unit": d.temperature_unit,
            "density_unit": d.density_unit,
            "product": {"id": getattr(p, "id", None), "name": getattr(p, "name", str(p))},
        }

    def get_role(self, obj):
        me = getattr(self.context["request"].user, "customer", None)
        if me and obj.shared_with_id == me.id:
            return "received"   # shared-with-me
        return "sent"           # shared-to-others

    def get_other_party(self, obj):
        """
        If I received it, other_party = owner (device.customer).
        If I sent it, other_party = recipient (shared_with).
        """
        me = getattr(self.context["request"].user, "customer", None)
        if me and obj.shared_with_id == me.id:
            other = getattr(obj.device, "customer", None)  # owner
        else:
            other = obj.shared_with  # recipient

        return {
            "id": getattr(other, "id", None),
            "email": getattr(other, "email", None),
        }
        
class DeviceThresholdRowSerializer(serializers.ModelSerializer):
    device_id = serializers.IntegerField(source="device_id_value", read_only=True)
    mapping_id = serializers.IntegerField(read_only=True)
    crop_id = serializers.IntegerField(read_only=True)
    crop_name = serializers.CharField(read_only=True)
    threshold_id = serializers.IntegerField(read_only=True, allow_null=True)
    moisture_threshold = serializers.FloatField(read_only=True, allow_null=True)
    scale_order_number = serializers.CharField(read_only=True)

    class Meta:
        model = ScaleCropMapping
        fields = [
            "device_id",
            "mapping_id",
            "scale_order_number",
            "crop_id",
            "crop_name",
            "threshold_id",
            "moisture_threshold",
        ]

    def get_device_id(self, obj):
        return self.context.get("device_id")

class ThresholdItemSerializer(serializers.Serializer):
    crop_name = serializers.CharField()
    moisture_threshold = serializers.DecimalField(max_digits=5, decimal_places=2)

class ThresholdItemListSerializer(serializers.ListSerializer):
    """
    Bulk updater: updates thresholds for a specific device by crop_name (case-insensitive).
    NOTE: When used with many=True, the view receives this ListSerializer directly.
    """

    @transaction.atomic
    def update_for_device(self, device_id: int, items):
        # 1) normalize payload (last write wins per crop_name)
        wanted = {}
        for it in items:
            key = it["crop_name"].strip().lower()
            wanted[key] = it["moisture_threshold"]

        names_lower = list(wanted.keys())
        if not names_lower:
            return {"updated": 0, "not_found": [], "total_requested": 0}

        # 2) fetch thresholds for this device and these crops (case-insensitive)
        qs = (
            MyDeviceMoistureThreshold.objects
            .select_related("mapping__crop")
            .filter(device_id=device_id)
            .annotate(crop_name_lower=Lower("mapping__crop__name"))
            .filter(crop_name_lower__in=names_lower)
        )

        found_by_name = {t.crop_name_lower: t for t in qs}

        to_update, not_found = [], []
        for name_lc, value in wanted.items():
            obj = found_by_name.get(name_lc)
            if obj is None:
                not_found.append(name_lc)
            else:
                obj.moisture_threshold = value
                to_update.append(obj)

        if to_update:
            MyDeviceMoistureThreshold.objects.bulk_update(to_update, ["moisture_threshold"])

        return {
            "updated": len(to_update),
            "not_found": not_found,
            "total_requested": len(items),
        }

class MoistureThresholdBulkUpdateByCropSerializer(ThresholdItemSerializer):
    class Meta:
        list_serializer_class = ThresholdItemListSerializer


class MoistureThresholdSingleUpdateByCropSerializer(serializers.Serializer):
    crop_name = serializers.CharField()
    moisture_threshold = serializers.DecimalField(max_digits=5, decimal_places=2)
    
    
class ThresholdByOrderItemSerializer(serializers.Serializer):
    scale_order_number = serializers.CharField()
    moisture_threshold = serializers.DecimalField(max_digits=5, decimal_places=2)

    def validate_scale_order_number(self, v):
        v = (v or "").strip()
        if not v:
            raise serializers.ValidationError("required")
        return v


class ThresholdByOrderListSerializer(serializers.ListSerializer):
    """
    Bulk updater: updates thresholds for a specific device by scale_order_number (case-insensitive),
    but ONLY within the device.scale mappings.
    """

    @transaction.atomic
    def update_for_device(self, device, items):
        if not device.scale_id:
            raise serializers.ValidationError({
                "code": "DEVICE_SCALE_NOT_SET",
                "errors": {"scale": ["This device has no scale set."]}
            })

        # normalize payload (last write wins per scale_order_number)
        wanted = {}
        for it in items:
            key = it["scale_order_number"].strip().lower()
            wanted[key] = it["moisture_threshold"]

        keys = list(wanted.keys())
        if not keys:
            return {"updated": 0, "not_found": [], "total_requested": 0}

        # fetch thresholds for this device for mappings under device.scale and requested order numbers
        qs = (
            MyDeviceMoistureThreshold.objects
            .select_related("mapping")
            .filter(device=device, mapping__scale=device.scale)
            .annotate(order_lc=Lower("mapping__scale_order_number"))
            .filter(order_lc__in=keys)
        )

        found = {t.order_lc: t for t in qs}

        to_update, not_found = [], []
        for order_lc, value in wanted.items():
            obj = found.get(order_lc)
            if obj is None:
                not_found.append(order_lc)
            else:
                obj.moisture_threshold = value
                to_update.append(obj)

        if to_update:
            MyDeviceMoistureThreshold.objects.bulk_update(to_update, ["moisture_threshold"])

        return {
            "updated": len(to_update),
            "not_found": not_found,
            "total_requested": len(items),
        }


class MoistureThresholdBulkUpdateByOrderSerializer(ThresholdByOrderItemSerializer):
    class Meta:
        list_serializer_class = ThresholdByOrderListSerializer


class MoistureThresholdSingleUpdateByOrderSerializer(serializers.Serializer):
    scale_order_number = serializers.CharField()
    moisture_threshold = serializers.DecimalField(max_digits=5, decimal_places=2)

    def validate_scale_order_number(self, v):
        v = (v or "").strip()
        if not v:
            raise serializers.ValidationError("required")
        return v