from __future__ import annotations

from collections.abc import Iterable
from datetime import date

from guests.models import Guest
from guests.guest_defaults import normalize_istat_guest_defaults
from guests.istat_utils import apply_guest_istat_defaults
from rest_framework.exceptions import ValidationError
from services.country_utils import normalize_country_fields


GUEST_REPLACEMENT_DEFAULTS = {
    "full_name": "",
    "is_main_guest": False,
    "guest_type": None,
    "email": None,
    "phone": None,
    "date_of_birth": None,
    "country_of_birth": None,
    "city": None,
    "region": None,
    "gender": None,
    "tourism_type": None,
    "transport_type": None,
    "document_type": None,
    "id_number": None,
    "document_issue_date": None,
    "document_expiry_date": None,
    "document_issuing_country": None,
    "nationality": None,
    "address": None,
    "zip_code": None,
    "country": None,
    "language_preference": None,
    "special_requests": None,
    "guest_notes": None,
    "is_city_tax_exempt": False,
    "city_tax_exemption_reason": None,
    "city_tax_exemption_notes": None,
}


def _is_minor_guest(guest: Guest, minor_age_limit: int | None) -> bool:
    """Return True if the guest should be counted as a child."""
    # Explicit minor exemption set during check-in
    reason = (guest.city_tax_exemption_reason or "").strip().lower()
    if "minor" in reason:
        return True
    # Age-based detection when date_of_birth is available
    if minor_age_limit and guest.date_of_birth:
        today = date.today()
        years = today.year - guest.date_of_birth.year
        if (today.month, today.day) < (guest.date_of_birth.month, guest.date_of_birth.day):
            years -= 1
        return years < minor_age_limit
    return False


def _update_booking_guest_counts(booking, synced_guests: list[Guest]) -> None:
    """Recalculate and persist adults_count / children_count from actual guest records."""
    try:
        minor_age_limit = booking.structure.city_tax_settings.minor_age_limit
    except Exception:
        minor_age_limit = None

    children = sum(1 for g in synced_guests if _is_minor_guest(g, minor_age_limit))
    adults = len(synced_guests) - children

    booking.adults_count = adults
    booking.children_count = children
    booking.save(update_fields=["adults_count", "children_count"])


def sync_booking_guests(
    *,
    booking,
    guests_data: Iterable[dict],
    update_booking_counts: bool = False,
) -> list[Guest]:
    ordered_guests = sorted(
        guests_data,
        key=lambda guest: not bool(guest.get("is_main_guest")),
    )
    total_guests = len(ordered_guests)

    existing_guests = list(Guest.objects.filter(booking=booking).order_by("id"))
    existing_by_id = {guest.id: guest for guest in existing_guests}
    remaining_guests = existing_guests.copy()
    synced_guests: list[Guest] = []
    seen_guest_ids = set()

    for index, guest_data in enumerate(ordered_guests):
        payload = apply_guest_istat_defaults(
            guest_data,
            booking=booking,
            index=index,
            total_guests=total_guests,
        )
        payload = normalize_istat_guest_defaults(
            payload,
            extra_data=payload.get("extra_data") if isinstance(payload.get("extra_data"), dict) else None,
        )
        payload = normalize_country_fields(payload)
        guest_id = payload.pop("id", None)
        extra_data = normalize_country_fields(payload.pop("extra_data", {}) or {})

        guest = None
        if guest_id is not None and guest_id not in existing_by_id:
            raise ValidationError(
                {"guests": [f"Guest ID {guest_id} does not belong to booking {booking.id}."]}
            )
        if guest_id in seen_guest_ids:
            raise ValidationError(
                {"guests": [f"Guest ID {guest_id} appears more than once in the payload."]}
            )

        if guest_id in existing_by_id:
            guest = existing_by_id[guest_id]
            if guest in remaining_guests:
                remaining_guests.remove(guest)
        elif remaining_guests:
            guest = remaining_guests.pop(0)

        replacement_values = {
            field: payload.get(field, default)
            for field, default in GUEST_REPLACEMENT_DEFAULTS.items()
        }
        replacement_values["extra_data"] = extra_data

        if guest is None:
            guest = Guest.objects.create(
                booking=booking,
                **replacement_values,
            )
        else:
            for field, value in replacement_values.items():
                setattr(guest, field, value)
            guest.booking = booking
            guest.save()

        synced_guests.append(guest)
        seen_guest_ids.add(guest.id)

    Guest.objects.filter(booking=booking).exclude(id__in=seen_guest_ids).delete()

    if update_booking_counts:
        _update_booking_guest_counts(booking, synced_guests)

    return synced_guests
