"""Validation helpers for Ross1000 payload assembly.

All validation raises Ross1000ValidationError with a descriptive message.
No silent swallowing of errors — every missing field is surfaced explicitly.
"""

from __future__ import annotations

from datetime import date

from istat.ross1000.exceptions import (
    Ross1000GuestError,
    Ross1000StructureError,
    Ross1000ValidationError,
)
from istat.ross1000.models.country_codes import get_ross1000_country_code

# Valid gender values accepted by Ross1000
_VALID_GENDERS = {"male", "m", "1", "female", "f", "2"}


def _blank(value: object) -> bool:
    if value is None:
        return True
    if isinstance(value, str):
        return not value.strip()
    return False


def require(value: object, field: str, context: str = "") -> None:
    """Raise Ross1000ValidationError if value is blank/None."""
    if _blank(value):
        prefix = f"[{context}] " if context else ""
        raise Ross1000ValidationError(f"{prefix}{field} is required for Ross1000 export")


def validate_structure(structure) -> None:
    """Validate that a structure has all required Ross1000 fields."""
    istat_code = str(getattr(structure, "istat_code", "") or "").strip()
    if not istat_code:
        raise Ross1000StructureError(
            f"Structure {getattr(structure, 'id', '?')} is missing istat_code"
        )


def validate_guest_for_ross1000(guest, *, booking_id: int) -> None:
    """Validate that a guest has all required Ross1000 fields.

    Raises Ross1000GuestError with a descriptive message on the first
    missing required field.
    """
    ctx = f"guest {getattr(guest, 'id', '?')} booking {booking_id}"

    # full_name
    full_name = str(getattr(guest, "full_name", "") or "").strip()
    if not full_name:
        raise Ross1000GuestError(f"[{ctx}] full_name is required")

    # date_of_birth
    dob = getattr(guest, "date_of_birth", None)
    if dob is None:
        raise Ross1000GuestError(f"[{ctx}] date_of_birth is required")

    # gender / sesso
    gender_raw = str(getattr(guest, "gender", "") or "").strip().lower()
    if gender_raw not in _VALID_GENDERS:
        raise Ross1000GuestError(
            f"[{ctx}] gender is required and must be 'male' or 'female', "
            f"got '{gender_raw or '(empty)'}'"
        )

    # country_of_birth
    cob = str(getattr(guest, "country_of_birth", "") or "").strip().upper()
    if not cob:
        raise Ross1000GuestError(f"[{ctx}] country_of_birth is required")
    if get_ross1000_country_code(cob) is None:
        raise Ross1000GuestError(
            f"[{ctx}] no Ross1000 country code for country_of_birth='{cob}'"
        )

    # country (residence)
    country = str(getattr(guest, "country", "") or "").strip().upper()
    if not country:
        raise Ross1000GuestError(f"[{ctx}] country (residence) is required")
    if get_ross1000_country_code(country) is None:
        raise Ross1000GuestError(
            f"[{ctx}] no Ross1000 country code for country='{country}'"
        )

    # nationality / citizenship
    nationality = str(getattr(guest, "nationality", "") or "").strip().upper()
    if not nationality:
        raise Ross1000GuestError(f"[{ctx}] nationality (citizenship) is required")
    if get_ross1000_country_code(nationality) is None:
        raise Ross1000GuestError(
            f"[{ctx}] no Ross1000 country code for nationality='{nationality}'"
        )


def validate_idswh(idswh: str | None, *, guest_id: int, booking_id: int) -> str:
    """Validate and return the idswh token.

    Raises Ross1000ValidationError if idswh is missing — the token must
    already exist in GuestStay; it is never generated here.
    """
    if _blank(idswh):
        raise Ross1000ValidationError(
            f"idswh is missing for guest {guest_id} / booking {booking_id}. "
            "Ensure GuestStay records exist before exporting."
        )
    return str(idswh).strip()


def validate_no_duplicate_idswh(
    payloads: list,
    *,
    context: str = "export",
) -> None:
    """Raise Ross1000ValidationError if any idswh appears more than once.

    Args:
        payloads: List of Ross1000GuestPayload instances.
        context:  Human-readable label for error messages.
    """
    seen: set[str] = set()
    for p in payloads:
        token = getattr(p, "idswh", None)
        if token in seen:
            raise Ross1000ValidationError(
                f"Duplicate idswh '{token}' detected in {context}. "
                "Each guest stay must have a unique regulatory identity token."
            )
        if token:
            seen.add(token)


def validate_date_range(start_date: date | None, end_date: date | None) -> tuple[date, date]:
    """Validate export date range."""
    if start_date is None:
        raise Ross1000ValidationError("start_date is required")
    if end_date is None:
        raise Ross1000ValidationError("end_date is required")
    if not isinstance(start_date, date):
        raise Ross1000ValidationError("start_date must be a date instance")
    if not isinstance(end_date, date):
        raise Ross1000ValidationError("end_date must be a date instance")
    if end_date < start_date:
        raise Ross1000ValidationError("end_date must be >= start_date")
    return start_date, end_date
