"""Template rendering (task 4.3, spec messaging FR-6.3, FR-6.4).

Placeholders are `{{name}}`. Deliberately not Jinja: these templates are edited by the host
through the admin UI, and a full template engine there would be a code-execution surface
for very little gain.

Unknown placeholders are left intact rather than blanked, so a typo shows up as
`{{even_title}}` in a preview instead of silently producing an empty sentence.
"""

import re
from dataclasses import dataclass
from datetime import datetime
from zoneinfo import ZoneInfo

from app.models.enums import Locale

PLACEHOLDER = re.compile(r"\{\{\s*(\w+)\s*\}\}")

#: Every placeholder the PRD defines (FR-6.3).
KNOWN_PLACEHOLDERS = frozenset(
    {
        "guest_name",
        "event_title",
        "event_date",
        "event_time",
        "venue",
        "days_left",
        "invite_url",
        "cancel_url",
        "map_url",
        "couple_names",
        "unsubscribe_url",
    }
)

#: Links must stay under this so an SMS remains 1-2 segments when v2 adds that channel.
MAX_LINK_LENGTH = 60


#: Appended to every message that does not place `{{unsubscribe_url}}` itself (task 3.6).
#:
#: The wording carries real weight. Suppression is per event (design D11), so a footer
#: promising to stop "these emails" would be a lie the moment the same person is invited to
#: a second ceremony. Both variants name the event and say `only`.
UNSUBSCRIBE_FOOTER = {
    Locale.EN: (
        "You are receiving this because you are on the guest list for {event}.\n"
        "To stop emails about {event} only: {url}"
    ),
    Locale.BN: (
        "{event}-এর অতিথি তালিকায় আপনার নাম থাকায় এই বার্তাটি পাঠানো হয়েছে।\n"
        "শুধু {event} সংক্রান্ত ইমেইল বন্ধ করতে: {url}"
    ),
}


def append_unsubscribe(body: str, *, event_title: str, url: str, locale: Locale) -> str:
    """Add the opt-out footer unless the template already placed the link itself.

    A host who writes `{{unsubscribe_url}}` into their own copy has chosen where it goes;
    appending a second one would show the guest two competing links.
    """
    if url in body:
        return body
    footer = UNSUBSCRIBE_FOOTER.get(locale, UNSUBSCRIBE_FOOTER[Locale.EN])
    return f"{body.rstrip()}\n\n—\n{footer.format(event=event_title, url=url)}"


@dataclass(frozen=True)
class RenderedMessage:
    subject: str
    html: str
    text: str
    missing_placeholders: list[str]


def render(template: str, variables: dict[str, str]) -> tuple[str, list[str]]:
    """Substitute placeholders, returning the text and any that had no value."""
    missing: list[str] = []

    def _replace(match: re.Match[str]) -> str:
        key = match.group(1)
        if key in variables and variables[key] is not None:
            return str(variables[key])
        missing.append(key)
        return match.group(0)  # leave it visible

    return PLACEHOLDER.sub(_replace, template), missing


def build_variables(
    *,
    guest_name: str,
    event_title: str,
    starts_at: datetime,
    venue: str,
    invite_url: str,
    cancel_url: str,
    map_url: str | None,
    couple_names: str,
    unsubscribe_url: str = "",
    timezone: str = "Asia/Dhaka",
    locale: Locale = Locale.EN,
    now: datetime | None = None,
) -> dict[str, str]:
    """Assemble the placeholder values.

    Dates render in the event's timezone, not the server's or the reader's — the wedding
    happens in Dhaka regardless of where the guest opens their email.
    """
    tz = ZoneInfo(timezone)
    local = starts_at.astimezone(tz)
    reference = (now or datetime.now(tz)).astimezone(tz)
    days_left = max(0, (local.date() - reference.date()).days)

    date_locale = "bn-BD" if locale is Locale.BN else "en-GB"  # informational only
    del date_locale

    return {
        "guest_name": guest_name,
        "event_title": event_title,
        "event_date": local.strftime("%A, %d %B %Y"),
        "event_time": local.strftime("%I:%M %p").lstrip("0"),
        "venue": venue,
        "days_left": str(days_left),
        "invite_url": invite_url,
        "cancel_url": cancel_url,
        "map_url": map_url or "",
        "couple_names": couple_names,
        "unsubscribe_url": unsubscribe_url,
    }


def to_html(text: str, *, preview_block: str = "") -> str:
    """Wrap a plain-text body in a minimal responsive email.

    Kept deliberately simple: email clients are hostile to modern CSS, and a plain-text
    alternative is required anyway or the message looks like spam (§8.3).

    `preview_block` is markup the system built (`link_preview.email_block`) and is placed
    above the body. It is a separate parameter rather than something the caller prepends to
    `text` because this function escapes its entire input — markup arriving through `text`
    would reach the guest as visible angle brackets. Inserting it after the escape pass is
    the whole point of the split.
    """
    escaped = text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
    # Turn bare URLs into links after escaping, so the anchor is not itself escaped.
    linked = re.sub(
        r"(https?://[^\s<]+)",
        r'<a href="\1" style="color:#7a5c2e">\1</a>',
        escaped,
    ).replace("\n", "<br>")

    return (
        '<!doctype html><html><body style="margin:0;padding:24px;'
        "font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;"
        'font-size:16px;line-height:1.6;color:#2b2b2b;background:#faf7f2">'
        '<div style="max-width:560px;margin:0 auto;background:#fff;border-radius:12px;'
        f'padding:28px">{preview_block}{linked}</div></body></html>'
    )


def check_link_lengths(text: str) -> list[str]:
    """Return links that exceed the SMS-safe budget (FR-6.4)."""
    return [u for u in re.findall(r"https?://\S+", text) if len(u) > MAX_LINK_LENGTH]
