"""Request and response models for RSVP submission, decline and cancellation."""

import uuid
from datetime import datetime

from pydantic import BaseModel, EmailStr, Field, field_validator

from app.models.enums import EventType, InvitationStatus, RsvpResponse
from app.services.phone import InvalidPhoneNumberError, normalize_phone

MAX_MESSAGE_LENGTH = 500


class _ContactFields(BaseModel):
    """Shared normalisation so every entry path stores identical values."""

    name: str = Field(min_length=1, max_length=200)
    phone: str | None = None
    email: EmailStr | None = None

    @field_validator("phone")
    @classmethod
    def _normalize_phone(cls, value: str | None) -> str | None:
        if value is None or not value.strip():
            return None
        try:
            return normalize_phone(value)
        except InvalidPhoneNumberError as exc:
            # Surfaces as a 422 with a field-level message the form renders inline.
            raise ValueError(str(exc)) from exc

    @field_validator("name")
    @classmethod
    def _strip_name(cls, value: str) -> str:
        cleaned = value.strip()
        if not cleaned:
            raise ValueError("Name is required")
        return cleaned


class RsvpAcceptRequest(_ContactFields):
    """Accept from a tokenized invitation (FR-2.2)."""

    party_size: int = Field(default=1, ge=1)
    adults: int | None = Field(default=None, ge=0)
    children: int | None = Field(default=None, ge=0)
    message_to_couple: str | None = Field(default=None, max_length=MAX_MESSAGE_LENGTH)
    dietary_notes: str | None = Field(default=None, max_length=1000)


class OpenRsvpRequest(RsvpAcceptRequest):
    """Accept from the open/QR link, where the guest is unknown (FR-2.10).

    Phone is required here: it is the only reliable key for merging this submission into an
    existing guest instead of creating a duplicate.
    """

    phone: str
    event_types: list[EventType] = Field(min_length=1)
    turnstile_token: str | None = None


class RsvpDeclineRequest(BaseModel):
    """Declining needs no form (FR-2.6)."""

    reason: str | None = Field(default=None, max_length=500)


class RsvpCancelRequest(BaseModel):
    """Cancellation always requires an explicit confirmation flag, so a link-preview
    crawler cannot produce a valid body by accident (FR-3.2, FR-3.3)."""

    confirm: bool
    reason: str | None = Field(default=None, max_length=500)

    @field_validator("confirm")
    @classmethod
    def _must_confirm(cls, value: bool) -> bool:
        if not value:
            raise ValueError("Cancellation requires explicit confirmation")
        return value


class RsvpConfirmation(BaseModel):
    """What the success screen renders (FR-2.7)."""

    invitation_id: uuid.UUID
    status: InvitationStatus
    response: RsvpResponse | None
    party_size: int | None
    event_title_en: str
    event_title_bn: str
    starts_at: datetime
    venue_name: str
    cancel_url: str
    calendar_url: str
    confirmation_email_queued: bool


class OpenRsvpConfirmation(BaseModel):
    """Open-link submissions can create several invitations at once."""

    guest_id: uuid.UUID
    merged_into_existing_guest: bool
    confirmations: list[RsvpConfirmation]
