"""Build normalized ISTAT XML-ready payload objects.

This module intentionally stops at typed payload creation. It does not produce
XML strings, files, ZIP archives, endpoints, or TXT export data.
"""

from __future__ import annotations

import json
from collections.abc import Iterable
from datetime import date, timedelta
from typing import Any

from rest_framework.exceptions import ValidationError as DrfValidationError

from guests.guest_defaults import istat_not_specified_export_value
from istat.municipalities import resolve_residence
from istat.xml_export.exceptions import XmlPayloadValidationError
from istat.xml_export.payloads.guest_payload import IstatXmlGuestPayload
from istat.xml_export.services.guest_export_normalization import (
    normalize_guest_for_istat_export,
    validate_guest_for_istat_export,
)
from istat.xml_export.services.lookup_service import IstatLookupService
from istat.xml_export.validators.payload_validator import (
    require_value,
    validate_payload,
    validate_residence_payload,
    validate_stay_dates,
)
from services.guest_night_service import generate_guest_nights


def _normalize_code(value: Any) -> str:
    return (str(value) if value is not None else "").strip().upper()


def _normalize_optional_text(value: Any) -> str | None:
    normalized = (str(value) if value is not None else "").strip()
    return normalized or None


def _get_extra_data(guest: Any) -> dict:
    extra_data = getattr(guest, "extra_data", None)
    if isinstance(extra_data, dict):
        return extra_data
    if isinstance(extra_data, str):
        try:
            parsed = json.loads(extra_data)
        except (TypeError, ValueError, json.JSONDecodeError):
            return {}
        return parsed if isinstance(parsed, dict) else {}
    return {}


def _first_present(*values: Any) -> Any:
    for value in values:
        if value is None:
            continue
        if isinstance(value, str) and not value.strip():
            continue
        return value
    return None


def _get_guest_field(guest: Any, field_name: str, *extra_keys: str) -> Any:
    extra_data = _get_extra_data(guest)
    return _first_present(
        getattr(guest, field_name, None),
        *(extra_data.get(key) for key in extra_keys or (field_name,)),
    )


def _get_guest_province(guest: Any) -> str:
    extra_data = _get_extra_data(guest)
    return _normalize_code(
        _first_present(
            extra_data.get("residence_province"),
            extra_data.get("province"),
            extra_data.get("state"),
            getattr(guest, "region", None),
        )
    )


def _get_guest_city(guest: Any) -> str | None:
    extra_data = _get_extra_data(guest)
    return _first_present(
        getattr(guest, "city", None),
        extra_data.get("city"),
        extra_data.get("residence_city"),
        extra_data.get("residence_municipality"),
        extra_data.get("place_of_birth"),
    )


def normalize_gender(gender: str | None) -> str:
    """Normalize guest gender into the XML payload representation."""
    normalized = _normalize_code(gender)
    gender_mappings = {
        "MALE": "M",
        "M": "M",
        "FEMALE": "F",
        "F": "F",
    }
    return gender_mappings.get(normalized, normalized)


def resolve_residence_payload(
    country: str | None,
    province: str | None,
    municipality: str | None = None,
) -> dict[str, Any]:
    """Resolve residence fields for the XML guest payload."""
    residence_country_iso2 = _normalize_code(country)
    require_value(residence_country_iso2, "country")

    if residence_country_iso2 == "IT":
        try:
            residence = resolve_residence(
                country=residence_country_iso2,
                city=municipality,
                province=province,
                require_for_italy=True,
            )
        except DrfValidationError as exc:
            raise XmlPayloadValidationError(str(exc.detail)) from exc

        province_sigla = _normalize_code(residence.province_code)
        require_value(province_sigla, "province")
        province_mapping = IstatLookupService.get_province(province_sigla)
        if province_mapping is None:
            raise XmlPayloadValidationError(
                f"Missing ISTAT province mapping for '{province_sigla}'"
            )

        region_code = province_mapping.get("region_code")
        if IstatLookupService.get_region_by_istat_code(region_code) is None:
            raise XmlPayloadValidationError(
                f"Missing ISTAT region mapping for '{region_code}'"
            )

        residence_payload = {
            "is_italian_resident": True,
            "residence_country_iso2": residence_country_iso2,
            "residence_country_code": None,
            "province_sigla": province_sigla,
            "province_code": province_mapping["province_code"],
            "region_code": region_code,
        }
    else:
        country_mapping = IstatLookupService.get_country(residence_country_iso2)
        if country_mapping is None:
            raise XmlPayloadValidationError(
                f"Missing ISTAT country mapping for '{residence_country_iso2}'"
            )

        residence_payload = {
            "is_italian_resident": False,
            "residence_country_iso2": residence_country_iso2,
            "residence_country_code": country_mapping["istat_code"],
            "province_sigla": None,
            "province_code": None,
            "region_code": None,
        }

    validate_residence_payload(**residence_payload)
    return residence_payload


def _resolve_arrival_date(booking: Any, guest_night: Any | None) -> date | None:
    if guest_night is not None and getattr(guest_night, "is_arrival_night", False):
        return getattr(guest_night, "date", None)
    return getattr(booking, "check_in_date", None)


def _resolve_departure_date(booking: Any, guest_night: Any | None) -> date | None:
    if guest_night is not None and getattr(guest_night, "is_departure_night", False):
        night_date = getattr(guest_night, "date", None)
        return night_date + timedelta(days=1) if night_date else None
    return getattr(booking, "check_out_date", None)


def build_guest_payload(
    *,
    booking: Any,
    guest: Any,
    guest_night: Any | None = None,
) -> IstatXmlGuestPayload:
    """Build a normalized, validated ISTAT XML-ready guest payload."""
    guest = normalize_guest_for_istat_export(guest)
    validate_guest_for_istat_export(guest)
    arrival_date = _resolve_arrival_date(booking, guest_night)
    departure_date = _resolve_departure_date(booking, guest_night)
    validate_stay_dates(arrival_date, departure_date)

    booking_id = getattr(booking, "id", None)
    guest_id = getattr(guest, "id", None)
    guest_type = _normalize_code(_get_guest_field(guest, "guest_type"))
    gender = normalize_gender(_get_guest_field(guest, "gender"))
    country = _normalize_code(_get_guest_field(guest, "country", "country"))
    province = _get_guest_province(guest)
    municipality = _get_guest_city(guest)

    require_value(booking_id, "booking_id")
    require_value(guest_id, "guest_id")
    require_value(guest_type, "guest_type")
    require_value(gender, "gender")
    require_value(country, "country")

    residence_payload = resolve_residence_payload(country, province, municipality)

    payload = IstatXmlGuestPayload(
        booking_id=booking_id,
        guest_id=guest_id,
        arrival_date=arrival_date,
        departure_date=departure_date,
        nights=(departure_date - arrival_date).days,
        gender=gender,
        guest_type=guest_type,
        tourism_type=_normalize_optional_text(
            istat_not_specified_export_value(getattr(guest, "tourism_type", None))
            or getattr(booking, "tourism_type", None)
        ),
        transport_type=_normalize_optional_text(
            istat_not_specified_export_value(getattr(guest, "transport_type", None))
            or getattr(booking, "transport_type", None)
        ),
        **residence_payload,
    )
    return validate_payload(payload)


def build_booking_payload(
    booking: Any,
    *,
    period_start: date | None = None,
    period_end_exclusive: date | None = None,
) -> list[IstatXmlGuestPayload]:
    """Build XML-ready guest payloads for all guests attached to a booking."""
    arrival_date = getattr(booking, "check_in_date", None)
    departure_date = getattr(booking, "check_out_date", None)
    validate_stay_dates(arrival_date, departure_date)

    guests = list(getattr(booking, "guests").all())
    if not guests:
        return []

    guest_nights = generate_guest_nights(
        [booking],
        period_start=period_start or arrival_date,
        period_end_exclusive=period_end_exclusive or departure_date,
    )
    arrival_nights_by_guest_id = {
        guest_night.guest_id: guest_night
        for guest_night in guest_nights
        if guest_night.is_arrival_night and guest_night.guest_id is not None
    }

    return [
        build_guest_payload(
            booking=booking,
            guest=guest,
            guest_night=arrival_nights_by_guest_id.get(getattr(guest, "id", None)),
        )
        for guest in guests
    ]
