"""
SiteTemplate services — business logic for template rendering and preview.
"""

import logging
import re
from typing import Dict, List, Optional

from jinja2.sandbox import SandboxedEnvironment
import jinja2
from sqlalchemy.orm import Session

from src.apps.site_templates.models.site_template import SiteTemplate

logger = logging.getLogger(__name__)

# Matches {{ variable_name }} with optional surrounding whitespace.
_VARIABLE_PATTERN = re.compile(r"\{\{[\s]*(\w+)[\s]*\}\}")


def render_template(template: SiteTemplate, variables: Dict[str, str]) -> Dict[str, str]:
    """
    Render a SiteTemplate's body_html and body_text using Jinja2.

    Uses a SandboxedEnvironment so that template code written by admins cannot
    access Python internals (e.g. ``{{ "".__class__.__mro__ }}``,
    ``{{ config.__class__.__init__.__globals__ }}``).  The sandbox raises
    ``SecurityError`` for any such access attempt.

    The render is permissive for undefined variables — they silently expand to
    an empty string — but all attribute/item access on values is restricted by
    the sandbox policy.

    Args:
        template:  The SiteTemplate ORM instance to render.
        variables: Key/value pairs to substitute at render time.

    Returns:
        {"html": rendered_html, "text": rendered_text}
    """
    env = SandboxedEnvironment(
        loader=jinja2.BaseLoader(),
        undefined=jinja2.Undefined,
    )

    html_body = template.body_html or ""
    text_body = template.body_text or ""

    try:
        rendered_html = env.from_string(html_body).render(**variables)
    except Exception as exc:
        logger.warning("Template render error (html) for key=%s: %s", template.template_key, exc)
        rendered_html = html_body

    try:
        rendered_text = env.from_string(text_body).render(**variables)
    except Exception as exc:
        logger.warning("Template render error (text) for key=%s: %s", template.template_key, exc)
        rendered_text = text_body

    return {"html": rendered_html, "text": rendered_text}


def validate_variables(template: SiteTemplate, variables: Dict[str, str]) -> List[str]:
    """
    Find variable names referenced in the template body that are NOT present
    in the supplied variables dict.

    Scans both body_html and body_text for {{ variable_name }} tokens, then
    returns the names of those that are absent from `variables`.

    Args:
        template:  The SiteTemplate ORM instance.
        variables: Dict of variables provided by the caller.

    Returns:
        List of variable names referenced in the template but not supplied.
    """
    combined_body = (template.body_html or "") + "\n" + (template.body_text or "")
    referenced = {m.group(1) for m in _VARIABLE_PATTERN.finditer(combined_body)}
    provided = set(variables.keys())
    return sorted(referenced - provided)


def preview_template(
    db: Session,
    template_id: int,
    variables: Dict[str, str],
) -> Optional[Dict]:
    """
    Load a template by ID, render it with the supplied variables, and report
    any variable names that were referenced but not provided.

    Args:
        db:          Database session.
        template_id: Primary key of the SiteTemplate to preview.
        variables:   Render-time substitution values.

    Returns:
        {"html": str, "text": str, "undefined_variables": list[str]}
        or None if the template does not exist.
    """
    from src.apps.site_templates import crud as site_template_crud

    template = site_template_crud.get_template_by_id(db, template_id)
    if not template:
        return None

    rendered = render_template(template, variables)
    undefined = validate_variables(template, variables)

    return {
        "html": rendered["html"],
        "text": rendered["text"],
        "undefined_variables": undefined,
    }
