"""What a link to an invitation says when it is shown somewhere else (design D5).

Three surfaces render a preview and they must not describe the same event differently: the
meta tags a chat application reads, the block composed into an invitation email, and the
admin panel that shows an admin what the guest will get. All three come from here.

The split that matters is **who renders the result**:

* `for_event` is the guest-free preview. It goes into the meta tags on `/i/{token}` and
  `/e/{slug}`, which a third party fetches and caches, so it is identical for every guest
  invited to the event and derivable from public event information alone (design D3).
* `for_guest` adds the guest's name. It is only ever used where the system itself does the
  rendering for one known recipient — their email, and the admin panel — so no crawler ever
  sees it.

`for_guest` is deliberately a separate function rather than an optional argument. An
argument that defaults to safe is a call site away from being unsafe; two functions make the
choice visible in the caller, which is where the decision actually lives.
"""

from dataclasses import dataclass
from datetime import datetime
from html import escape
from zoneinfo import ZoneInfo

from app.models.enums import Locale
from app.models.wedding import Event, Wedding
from app.schemas.card import PreviewImage, preview_image_of


@dataclass(frozen=True)
class PreviewImageRef:
    """Absolute URL plus the dimensions the meta tags declare, so a chat application can
    reserve the right box before the image itself arrives."""

    url: str
    width: int
    height: int


@dataclass(frozen=True)
class LinkPreview:
    title: str
    description: str
    #: Always the event's public page, never a tokenized URL — a platform caches and
    #: attributes the preview against this (design D3).
    canonical_url: str
    site_name: str
    image: PreviewImageRef | None
    #: Set only by `for_guest`. `None` on anything a third party can fetch.
    guest_name: str | None = None


def _couple_names(wedding: Wedding) -> str:
    return f"{wedding.bride_name} & {wedding.groom_name}"


def _title_for(event: Event, locale: Locale) -> str:
    return event.title_bn if locale is Locale.BN else event.title_en


def absolute(base_url: str, path: str) -> str:
    """Join the deployment's base URL to a path. Previews must be absolute — a relative URL
    in a meta tag is resolved against nothing by the crawler that reads it."""
    return f"{base_url.rstrip('/')}{path}"


def event_url(base_url: str, event: Event) -> str:
    return absolute(base_url, f"/e/{event.slug}")


def invitation_url(base_url: str, token: str) -> str:
    return absolute(base_url, f"/i/{token}")


def _describe(event: Event, wedding: Wedding) -> str:
    """Date in the wedding's timezone, then the venue.

    The date is the event's local one regardless of where it is read: the wedding happens in
    Dhaka whether the guest opens the link there or not.

    Formatted in English in both locales, matching `templating.build_variables`. The Bangla
    date format lands with the locale chain that is still outstanding across both changes;
    doing it here first would put two answers in the codebase for one question.
    """
    local: datetime = event.starts_at.astimezone(ZoneInfo(wedding.timezone))
    when = local.strftime("%A, %d %B %Y")
    at = local.strftime("%I:%M %p").lstrip("0")
    return f"{when} at {at} — {event.venue_name}"


def for_event(
    event: Event,
    wedding: Wedding,
    *,
    base_url: str,
    card_config: dict[str, object] | None = None,
) -> LinkPreview:
    """The preview every guest of this event shares.

    Note what is *not* a parameter: the guest, and their preferred locale. Resolving the
    title in a guest's own language would make two guests' meta tags differ, which is the
    one property the tokenized route depends on (spec invitation-page).
    """
    locale = wedding.default_locale
    stored: PreviewImage | None = _stored_image(card_config)

    return LinkPreview(
        title=f"{_title_for(event, locale)} — {_couple_names(wedding)}",
        description=_describe(event, wedding),
        canonical_url=event_url(base_url, event),
        site_name=_couple_names(wedding),
        image=(
            PreviewImageRef(
                url=absolute(base_url, stored.url),
                width=stored.width,
                height=stored.height,
            )
            if stored is not None
            else None
        ),
    )


def for_guest(
    event: Event,
    wedding: Wedding,
    *,
    guest_name: str,
    base_url: str,
    card_config: dict[str, object] | None = None,
) -> LinkPreview:
    """The same preview, addressed to one recipient.

    Only for surfaces the system renders itself for a single known guest. Everything else is
    identical to `for_event`, so the picture and the words a guest sees in their email match
    the ones anyone they forward the link to will see.
    """
    base = for_event(event, wedding, base_url=base_url, card_config=card_config)
    return LinkPreview(
        title=base.title,
        description=base.description,
        canonical_url=base.canonical_url,
        site_name=base.site_name,
        image=base.image,
        guest_name=guest_name,
    )


#: Matches the card's palette in `web/app/globals.css`. Inline and hard-coded because an
#: email carries no stylesheet — every colour has to travel in the markup.
_INK = "#2b2b2b"
_MUTED = "#6b6257"
_ACCENT = "#7a5c2e"
_EDGE = "#e6ddd961"


def email_block(preview: LinkPreview, *, invite_url: str) -> str:
    """The preview block placed above an invitation email's body (design D4).

    Composed here rather than left to the mail client: no mail client reads Open Graph tags,
    so a bare URL in an inbox stays a bare URL. What a chat application builds from meta
    tags, this builds by hand from the same model, so the two agree.

    Three constraints shape the markup, all of them email's rather than ours:

    * **Tables, not flexbox.** Outlook renders through Word's HTML engine, which supports
      neither flex nor grid.
    * **Inline styles only.** Gmail strips `<style>` blocks in several clients.
    * **Legible with no image at all.** Most clients block remote images until the reader
      allows them, so the title, date and venue are text above the picture rather than
      baked into it, and the image carries alt text naming the event. Images-off is the
      default case here, not the edge case.

    Everything interpolated is escaped: the values are a venue name and a couple's names,
    typed by a customer into an admin form.
    """
    title = escape(preview.title)
    description = escape(preview.description)
    url = escape(invite_url, quote=True)

    if preview.image is not None:
        # `display:block` kills the baseline gap under the image; the border-radius is
        # ignored by Outlook and that is fine, it degrades to a square corner.
        picture = (
            f'<tr><td style="padding:0"><a href="{url}" style="display:block">'
            f'<img src="{escape(preview.image.url, quote=True)}" alt="{title}" '
            f'width="{preview.image.width}" '
            'style="display:block;width:100%;max-width:560px;height:auto;border:0;'
            'border-radius:10px 10px 0 0"></a></td></tr>'
        )
    else:
        picture = ""

    greeting = (
        f'<p style="margin:0 0 6px;font-size:15px;color:{_MUTED}">'
        f"{escape(preview.guest_name)}," + "</p>"
        if preview.guest_name
        else ""
    )

    return (
        '<table role="presentation" cellpadding="0" cellspacing="0" border="0" '
        'style="width:100%;max-width:560px;margin:0 0 24px;border:1px solid '
        f'{_EDGE};border-radius:10px;border-collapse:separate">'
        f"{picture}"
        '<tr><td style="padding:18px 20px">'
        f"{greeting}"
        f'<a href="{url}" style="text-decoration:none;color:{_INK}">'
        f'<span style="display:block;font-size:18px;font-weight:600;line-height:1.3">'
        f"{title}</span>"
        f'<span style="display:block;margin-top:6px;font-size:14px;color:{_MUTED}">'
        f"{description}</span></a>"
        f'<a href="{url}" style="display:inline-block;margin-top:14px;font-size:15px;'
        f'font-weight:600;color:{_ACCENT};text-decoration:underline">View your invitation</a>'
        "</td></tr></table>"
    )


def _stored_image(card_config: dict[str, object] | None) -> PreviewImage | None:
    """An event with no published design, or one published without a picture, previews
    without an image rather than with a placeholder (spec link-preview)."""
    return preview_image_of(card_config)
