"""Strict, non-executable message templates."""

from __future__ import annotations

import html
import re
from dataclasses import dataclass


VARIABLE = re.compile(r"{{\s*([a-z][a-z0-9_]*)\s*}}")


@dataclass(frozen=True)
class TemplateDefinition:
    key: str
    channel: str
    version: int
    allowed_variables: tuple[str, ...]
    text: str
    subject: str | None = None
    html_body: str | None = None


BUILTIN_TEMPLATES = {
    ("security_login_code_sms", "sms"): TemplateDefinition(
        "security_login_code_sms", "sms", 1, ("code", "expiry_minutes"),
        "USG verification code: {{ code }}. Expires in {{ expiry_minutes }} minutes. Do not share this code.",
    ),
    ("security_login_code_email", "email"): TemplateDefinition(
        "security_login_code_email", "email", 1, ("code", "expiry_minutes"),
        "Your verification code is {{ code }}.\n\nIt expires in {{ expiry_minutes }} minutes.\n\nDo not share this code.",
        "Your USG verification code",
        "<p>Your verification code is <strong>{{ code }}</strong>.</p><p>It expires in {{ expiry_minutes }} minutes.</p><p>Do not share this code.</p>",
    ),
    ("password_reset_sms", "sms"): TemplateDefinition(
        "password_reset_sms", "sms", 1, ("code", "expiry_minutes"),
        "USG password reset code: {{ code }}. Expires in {{ expiry_minutes }} minutes. Do not share this code.",
    ),
    ("password_reset_email", "email"): TemplateDefinition(
        "password_reset_email", "email", 1, ("code", "expiry_minutes"),
        "Your password reset code is {{ code }}. It expires in {{ expiry_minutes }} minutes.",
        "Reset your USG password",
    ),
    ("generic_system_alert_sms", "sms"): TemplateDefinition(
        "generic_system_alert_sms", "sms", 1, ("alert",), "USG alert: {{ alert }}",
    ),
    ("generic_system_alert_email", "email"): TemplateDefinition(
        "generic_system_alert_email", "email", 1, ("alert",), "{{ alert }}", "USG system alert",
    ),
}


class TemplateError(ValueError):
    pass


def render_template(template: TemplateDefinition, data: dict) -> dict[str, str | None]:
    if not isinstance(data, dict):
        raise TemplateError("template_data must be an object")
    allowed = set(template.allowed_variables)
    supplied = set(data)
    missing, unexpected = allowed - supplied, supplied - allowed
    if missing:
        raise TemplateError(f"Missing template variables: {', '.join(sorted(missing))}")
    if unexpected:
        raise TemplateError(f"Unexpected template variables: {', '.join(sorted(unexpected))}")

    def substitute(source: str | None, *, escape_html: bool = False) -> str | None:
        if source is None:
            return None
        return VARIABLE.sub(
            lambda match: html.escape(str(data[match.group(1)]), quote=True) if escape_html else str(data[match.group(1)]),
            source,
        )

    return {
        "subject": substitute(template.subject),
        "text": substitute(template.text),
        "html": substitute(template.html_body, escape_html=True),
    }
