from django.db import transaction
from django.utils import timezone
from rest_framework import serializers
from .models import Guest
from guests.guest_sync_service import sync_booking_guests
from bookings.guest_limits import resolve_property_type, validate_guest_limit
from bookings.models import Booking
from datetime import date
import json
from services.country_utils import COUNTRY_FIELD_NAMES, normalize_country
from .validation import validate_italian_residence
from .guest_defaults import normalize_istat_guest_defaults


def _calculate_age(dob: date) -> int:
    today = date.today()
    years = today.year - dob.year
    if (today.month, today.day) < (dob.month, dob.day):
        years -= 1
    return years


# =====================================================
# GuestSerializer (USED FOR BOOKING READ / PREFILL)
# =====================================================
class GuestSerializer(serializers.ModelSerializer):
    """Guest serializer used by BookingSerializer (READ)"""
    id = serializers.IntegerField(required=False, allow_null=True)
    extra_data = serializers.JSONField(required=False)

    class Meta:
        model = Guest
        fields = [
            "id",
            "booking",

            # BASIC
            "full_name",
            "is_main_guest",
            "guest_type",
            "email",
            "phone",

            # CHECK-IN FIELDS 
            "date_of_birth",
            "country_of_birth",
            "gender",
            "tourism_type",
            "transport_type",
            "document_type",
            "document_issue_date",
            "document_expiry_date",
            "document_issuing_country",

            # IDENTIFICATION
            "nationality",
            "id_number",

            # ADDRESS
            "address",
            "zip_code",
            "country",
            "city",
            "region",

            # OTHER
            "language_preference",
            "guest_notes",
            "special_requests",
            "is_city_tax_exempt",
            "city_tax_exemption_reason",

            # DYNAMIC
            "extra_data",

            "created_at",
            "updated_at",
        ]
        read_only_fields = ["booking", "created_at", "updated_at"]

    def to_representation(self, instance):
        data = super().to_representation(instance)

        extra = instance.extra_data

        if isinstance(extra, str):
            try:
                extra = json.loads(extra)
            except Exception:
                extra = None

        if isinstance(extra, dict):
            for key, value in extra.items():
                if key not in data or data.get(key) in (None, ""):
                    data[key] = value

        for field_name in COUNTRY_FIELD_NAMES:
            if field_name in data:
                data[field_name] = normalize_country(data.get(field_name))

        return data

    def _normalize_country_field(self, value):
        return normalize_country(value)

    def validate_phone(self, value):
        if value and not value.replace("+", "").replace(" ", "").isdigit():
            raise serializers.ValidationError(
                "Phone number must contain only digits or '+' sign."
            )
        return value

    def validate_full_name(self, value):
        if value and not value.strip():
            raise serializers.ValidationError("Full name cannot be blank.")
        return value

    def validate_country_of_birth(self, value):
        return self._normalize_country_field(value)

    def validate_document_issuing_country(self, value):
        return self._normalize_country_field(value)

    def validate_nationality(self, value):
        return self._normalize_country_field(value)

    def validate_country(self, value):
        return self._normalize_country_field(value)

    def validate(self, attrs):
        instance = getattr(self, "instance", None)
        country = attrs.get("country", getattr(instance, "country", None))
        city = attrs.get("city", getattr(instance, "city", None))
        region = attrs.get("region", getattr(instance, "region", None))

        extra_data = attrs.get("extra_data", getattr(instance, "extra_data", {}) or {})
        province = extra_data.get("province") if isinstance(extra_data, dict) else None

        validate_italian_residence(
            country,
            province,
            city,
            region=region,
            extra_data=extra_data,
        )
        return normalize_istat_guest_defaults(attrs, extra_data=extra_data)


# =====================================================
# CheckInGuestSerializer (WRITE / VALIDATION ONLY)
# =====================================================
class CheckInGuestSerializer(serializers.ModelSerializer):
    """Accepts both model fields and unlimited dynamic fields."""
    id = serializers.IntegerField(required=False, allow_null=True)
    
    class Meta:
        model = Guest
        fields = [
            "id",
            "full_name",
            "is_main_guest",
            "guest_type",
            "email",
            "phone",
            "date_of_birth",
            "country_of_birth",
            "gender",
            "tourism_type",
            "transport_type",
            "document_type",
            "id_number",
            "document_issue_date",
            "document_expiry_date",
            "document_issuing_country",
            "nationality",
            "address",
            "zip_code",
            "country",
            "city",
            "region",
            "language_preference",
            "special_requests",
            "guest_notes",
            "is_city_tax_exempt",
            "city_tax_exemption_reason",
        ]
        read_only_fields = []

    def to_internal_value(self, data):
        known_fields = set(self.fields.keys())

        # fields that must NEVER go into extra_data
        system_fields = {
            "id",
            "booking",
            "created_at",
            "updated_at",
            "extra_data",
        }

        cleaned_data = {}
        extra_data = {}

        for key, value in data.items():
            if key in known_fields:
                cleaned_data[key] = value
            elif key not in system_fields:
                extra_data[key] = value

        internal = super().to_internal_value(cleaned_data)
        internal["extra_data"] = extra_data
        return internal

    def validate_full_name(self, value):
        if value is None:
            return value
        return value.strip()

    def validate_document_expiry_date(self, value):
        if value and value < date.today():
            raise serializers.ValidationError("Document has expired.")
        return value

    def validate_date_of_birth(self, value):
        if value and value >= date.today():
            raise serializers.ValidationError("Date of birth must be in the past.")
        return value

    def validate_country_of_birth(self, value):
        return normalize_country(value)

    def validate_document_issuing_country(self, value):
        return normalize_country(value)

    def validate_nationality(self, value):
        return normalize_country(value)

    def validate_country(self, value):
        return normalize_country(value)

    def to_representation(self, instance):
        data = super().to_representation(instance)

        extra = instance.extra_data
        if isinstance(extra, str):
            try:
                extra = json.loads(extra)
            except Exception:
                extra = None

        if isinstance(extra, dict):
            for key, value in extra.items():
                if key not in data or data.get(key) in (None, ""):
                    data[key] = value

        for field_name in COUNTRY_FIELD_NAMES:
            if field_name in data:
                data[field_name] = normalize_country(data.get(field_name))

        return data

    def validate(self, attrs):
        """Cross-field validation for Italian residence municipality rules."""
        country = attrs.get("country")
        city = attrs.get("city")
        region = attrs.get("region")
        extra_data = attrs.get("extra_data", {})
        province = extra_data.get("province") if isinstance(extra_data, dict) else None

        validate_italian_residence(
            country,
            province,
            city,
            region=region,
            extra_data=extra_data,
        )

        return normalize_istat_guest_defaults(attrs, extra_data=extra_data)


# =====================================================
# CheckInSerializer (PUBLIC CHECK-IN ENDPOINT)
# =====================================================
class CheckInSerializer(serializers.Serializer):
    booking_id = serializers.IntegerField(required=True)
    guests = CheckInGuestSerializer(many=True, required=True)

    def validate_booking_id(self, value):
        try:
            self._booking = Booking.objects.select_related(
                "property_type",
                "property__property_type",
            ).get(id=value)
        except Booking.DoesNotExist as exc:
            raise serializers.ValidationError(
                f"Booking with ID {value} does not exist."
            ) from exc
        return value

    def validate_guests(self, value):
        if not value:
            raise serializers.ValidationError("At least one guest is required.")

        main_guests = [g for g in value if g.get("is_main_guest")]
        if not main_guests:
            raise serializers.ValidationError("At least one main guest is required.")

        if len(main_guests) > 1:
            raise serializers.ValidationError("Only one main guest is allowed.")

        return value

    def validate(self, attrs):
        booking = getattr(self, "_booking", None)
        if booking is None:
            booking = Booking.objects.select_related(
                "property_type",
                "property__property_type",
            ).get(id=attrs["booking_id"])

        property_type = resolve_property_type(booking=booking)

        try:
            validate_guest_limit(
                property_type=property_type,
                total_guests=len(attrs["guests"]),
            )
        except ValueError as exc:
            raise serializers.ValidationError(str(exc))

        # Validate exemption reason against date of birth using the
        # structure's configured minor_age_limit.
        try:
            city_tax_settings = booking.structure.city_tax_settings
            minor_age_limit = city_tax_settings.minor_age_limit
        except Exception:
            city_tax_settings = None
            minor_age_limit = None

        if minor_age_limit is not None:
            for index, guest in enumerate(attrs["guests"]):
                reason = (guest.get("city_tax_exemption_reason") or "").strip().lower()
                dob = guest.get("date_of_birth")

                if "minor" in reason and dob:
                    age = _calculate_age(dob)
                    if age >= minor_age_limit:
                        guest_label = "Main guest" if index == 0 else f"Guest #{index + 1}"
                        raise serializers.ValidationError(
                            f"{guest_label}: Exemption reason is set to 'Minor' but the "
                            f"date of birth ({dob}) indicates an age of {age} years. "
                            f"The minor age limit for this property is {minor_age_limit}. "
                            f"Please correct the date of birth or change the exemption reason."
                        )

        attrs["booking"] = booking
        return attrs

    def create(self, validated_data):
        booking = validated_data.get("booking")
        booking_id = booking.id if booking is not None else validated_data["booking_id"]
        guests_data = validated_data["guests"]

        with transaction.atomic():
            booking = Booking.objects.select_for_update().get(id=booking_id)
            previous_is_checked_in = booking.is_checked_in
            created_guests = sync_booking_guests(
                booking=booking,
                guests_data=guests_data,
                update_booking_counts=True,
            )
            booking.is_checked_in = True
            booking.checked_in_at = timezone.now()
            booking._skip_receipt_sync = True
            try:
                booking.save(update_fields=["is_checked_in", "checked_in_at"])
            finally:
                booking._skip_receipt_sync = False

            if not previous_is_checked_in and booking.is_checked_in:
                from services.receipt_service import sync_receipt_for_booking

                sync_receipt_for_booking(booking)

        return {
            "booking_id": booking.id,
            "is_checked_in": booking.is_checked_in,
            "checked_in_at": booking.checked_in_at,
            "guests": created_guests,
        }


# =====================================================
# CheckInResponseSerializer
# =====================================================
class CheckInResponseSerializer(serializers.Serializer):
    booking_id = serializers.IntegerField()
    is_checked_in = serializers.BooleanField()
    checked_in_at = serializers.DateTimeField(allow_null=True)
    guests = GuestSerializer(many=True)
    message = serializers.CharField()
