"""Wedding and Event (PRD §4.2)."""

import uuid
from datetime import datetime
from typing import TYPE_CHECKING

from sqlalchemy import Boolean, ForeignKey, Integer, String, Text
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import Mapped, mapped_column, relationship

from app.models.base import Base, TimestampTZ, created_at_col, pg_enum, updated_at_col, uuid_pk
from app.models.enums import EventType, Locale

if TYPE_CHECKING:
    from app.models.guest import Guest, Invitation


class Wedding(Base):
    __tablename__ = "wedding"

    id: Mapped[uuid.UUID] = uuid_pk()
    bride_name: Mapped[str] = mapped_column(Text, nullable=False)
    groom_name: Mapped[str] = mapped_column(Text, nullable=False)
    slug: Mapped[str] = mapped_column(String(120), unique=True, nullable=False)
    default_locale: Mapped[Locale] = mapped_column(
        pg_enum(Locale, "locale"), default=Locale.EN, nullable=False
    )
    timezone: Mapped[str] = mapped_column(String(64), default="Asia/Dhaka", nullable=False)
    host_contact_phone: Mapped[str | None] = mapped_column(String(20))

    #: Where a guest's reply goes (add-guest-invitation-send D9). Only ever a `Reply-To` —
    #: the `From` stays the platform's authenticated sender, because SPF and DKIM authorise
    #: that domain and a couple's own address in `From` fails authentication and lands the
    #: invitation in spam. Absent means no reply address, which is what every send did before.
    host_email: Mapped[str | None] = mapped_column(String(320))

    #: Customer-editable greeting sentences, keyed `{locale}.{invitation_type}` — at most
    #: four short strings (design D7). JSONB rather than four columns so adding a locale is
    #: not a migration; the values are short, rarely written and never queried against, so
    #: the usual arguments against JSONB do not apply here.
    #:
    #: Absent keys fall back to built-in defaults in the greeting service. A guest must
    #: never be shown an empty greeting, so nothing reads this column directly.
    invitation_messages: Mapped[dict[str, dict[str, str]]] = mapped_column(
        JSONB, nullable=False, default=dict, server_default="{}"
    )

    created_at: Mapped[datetime] = created_at_col()

    # passive_deletes leaves the cascade to Postgres, which the FKs already declare.
    # Without it the ORM loads the children on delete and UPDATEs their foreign key to
    # NULL first — against a NOT NULL column that is an IntegrityError, so deleting a
    # wedding or an event fails outright instead of cascading.
    events: Mapped[list["Event"]] = relationship(back_populates="wedding", passive_deletes=True)


class Event(Base):
    """One of the three ceremonies. `starts_at` drives all reminder math (design D6)."""

    __tablename__ = "event"
    # No uniqueness on (wedding_id, type): events are created by admins with their own names,
    # so a wedding may hold two receptions or three Mehedi nights (design D12).

    id: Mapped[uuid.UUID] = uuid_pk()
    wedding_id: Mapped[uuid.UUID] = mapped_column(
        ForeignKey("wedding.id", ondelete="CASCADE"), nullable=False, index=True
    )

    #: Who may see this event and everything under it (add-admin-access-control design D1).
    #: A Host reaches exactly the events they own; a Super Admin reaches all of them. Guests
    #: carry no owner of their own — theirs is derived from here, so the two cannot disagree.
    #:
    #: RESTRICT, not SET NULL and not CASCADE. SET NULL leaves an event no query will ever
    #: surface: it exists and nobody can reach it. CASCADE deletes a customer's wedding
    #: because an employee left. RESTRICT forces transfer-then-remove, and the database
    #: enforces it even when the endpoint forgets to.
    owner_admin_id: Mapped[uuid.UUID] = mapped_column(
        ForeignKey("admin_user.id", ondelete="RESTRICT"), nullable=False, index=True
    )

    type: Mapped[EventType] = mapped_column(pg_enum(EventType, "event_type"), nullable=False)
    slug: Mapped[str] = mapped_column(String(60), unique=True, nullable=False)

    title_bn: Mapped[str] = mapped_column(Text, nullable=False)
    title_en: Mapped[str] = mapped_column(Text, nullable=False)

    starts_at: Mapped[datetime] = mapped_column(TimestampTZ, nullable=False)
    ends_at: Mapped[datetime | None] = mapped_column(TimestampTZ)

    venue_name: Mapped[str] = mapped_column(Text, nullable=False)
    venue_address: Mapped[str] = mapped_column(Text, nullable=False)
    map_url: Mapped[str | None] = mapped_column(Text)
    dress_code: Mapped[str | None] = mapped_column(Text)
    notes: Mapped[str | None] = mapped_column(Text)

    #: Who is holding this event — in practice a parent of the couple, one or two of them,
    #: never the bride and groom themselves. Per event rather than per wedding because two
    #: ceremonies of one wedding are routinely hosted by different families (design D1).
    #:
    #: `host_name_2` is NULL when there is no second host, never `""`. Two spellings of
    #: "absent" would mean every reader has to test for both forever, so the write paths
    #: coerce blank to NULL (design D3).
    host_name_1: Mapped[str] = mapped_column(Text, nullable=False)
    host_name_2: Mapped[str | None] = mapped_column(Text)
    #: E.164, normalised by `app.services.phone` exactly as a guest's number is — the
    #: invitation renders it as a `tel:` link, so an un-normalised number is an undialable
    #: one (design D4).
    host_phone: Mapped[str] = mapped_column(String(20), nullable=False)

    theme_key: Mapped[str] = mapped_column(String(40), default="classic", nullable=False)
    cover_image_url: Mapped[str | None] = mapped_column(Text)
    music_url: Mapped[str | None] = mapped_column(Text)

    rsvp_deadline: Mapped[datetime | None] = mapped_column(TimestampTZ)
    capacity: Mapped[int | None] = mapped_column(Integer)
    is_published: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)

    #: Greeting overrides for this event, same `{locale}.{invitation_type}` shape as the
    #: column on Wedding (design D9). Empty means "inherit", which is why the default is `{}`
    #: and no migration backfills anything.
    #:
    #: Keyed by locale even though only English is editable per event today: the identical
    #: shape is what lets one `greeting.resolve` walk this map and the wedding's with the same
    #: code, and it makes adding Bangla a UI change rather than a migration.
    invitation_messages: Mapped[dict[str, dict[str, str]]] = mapped_column(
        JSONB, nullable=False, default=dict, server_default="{}"
    )

    created_at: Mapped[datetime] = created_at_col()
    updated_at: Mapped[datetime] = updated_at_col()

    wedding: Mapped[Wedding] = relationship(back_populates="events")
    invitations: Mapped[list["Invitation"]] = relationship(
        back_populates="event", passive_deletes=True
    )
    #: Declared so deleting an event cascades to its guest list in the database (design D11).
    guests: Mapped[list["Guest"]] = relationship(back_populates="event", passive_deletes=True)
