"""Phone normalisation to E.164 (spec rsvp-flow FR-2.3, guest-management).

Every phone number in the system is stored in one format so that deduplication and
messaging can rely on string equality. Bangladesh is the default region: guests type
`01712345678` and mean `+8801712345678`.
"""

import phonenumbers

DEFAULT_REGION = "BD"


class InvalidPhoneNumberError(ValueError):
    """Raised when a number cannot be parsed or is not a valid number for its region."""


def normalize_phone(raw: str | None, region: str = DEFAULT_REGION) -> str | None:
    """Return the E.164 form, or None for empty input.

    Raises InvalidPhoneNumberError for input that is present but unusable, so a caller can
    surface an inline field error rather than silently storing something unsendable.
    """
    if raw is None:
        return None

    cleaned = raw.strip()
    if not cleaned:
        return None

    try:
        parsed = phonenumbers.parse(cleaned, region)
    except phonenumbers.NumberParseException as exc:
        raise InvalidPhoneNumberError(f"Could not parse phone number: {cleaned}") from exc

    if not phonenumbers.is_valid_number(parsed):
        raise InvalidPhoneNumberError(f"Not a valid phone number: {cleaned}")

    return phonenumbers.format_number(parsed, phonenumbers.PhoneNumberFormat.E164)


def try_normalize_phone(raw: str | None, region: str = DEFAULT_REGION) -> str | None:
    """Normalise, returning None instead of raising. For bulk import, where one bad row
    must not abort the batch."""
    try:
        return normalize_phone(raw, region)
    except InvalidPhoneNumberError:
        return None


def normalize_email(raw: str | None) -> str | None:
    """Lowercase and trim. Uniqueness is enforced on the lowercased value, so every write
    path must agree on the same normalisation."""
    if raw is None:
        return None
    cleaned = raw.strip().lower()
    return cleaned or None
