# Copyright 2024 The Debusine Developers
# See the AUTHORS file at the top-level directory of this distribution
#
# This file is part of Debusine. It is subject to the license terms
# in the LICENSE file found in the top-level directory of this
# distribution. No part of Debusine, including this file, may be copied,
# modified, propagated, or distributed except according to the terms
# contained in the LICENSE file.

"""Template tag library to render Debusine UI elements."""

import logging
from datetime import datetime

from django import template
from django.conf import settings
from django.db.models import Model
from django.template import Context, Node, TemplateSyntaxError
from django.utils.html import format_html

from debusine.server.scopes import urlconf_scope
from debusine.web.views.base import Widget
from debusine.web.views.ui_shortcuts import UIShortcut

logger = logging.getLogger("debusine.web")

register = template.Library()


@register.simple_tag
def ui_shortcuts(obj: Model) -> list[UIShortcut]:
    """Return the stored UI shortcuts for the given object."""
    stored = getattr(obj, "_ui_shortcuts", None)
    if stored is None:
        return []
    return stored


@register.simple_tag(takes_context=True)
def widget(context, widget: Widget) -> str:
    """Render a UI widget."""
    try:
        return widget.render(context)
    except Exception:
        if settings.DEBUG:
            raise
        # When running in production, avoid leaking possibly sensitive error
        # information while providing enough information for a potential bug
        # report to locate the stack trace in the logs
        logger.warning("widget %r failed to render", widget, exc_info=True)
        return format_html(
            "<span class='bg-danger text-white'>{ts} UTC: {widget}"
            " failed to render</span>",
            widget=widget.__class__.__name__,
            ts=datetime.utcnow().isoformat(),
        )


class WithScopeNode(Node):
    """Template node implementing the {% withscope %} tag."""

    def __init__(self, value, nodelist):
        """Store node arguments."""
        self.value = value
        self.nodelist = nodelist

    def render(self, context: Context) -> str:
        """Render the template node."""
        from debusine.db.context import context as appcontext
        from debusine.db.models import Scope

        # Resolve the argument token to a value
        value: str | Scope = self.value.resolve(context)

        # Resolve a scope name to a Scope object
        match value:
            case str():
                try:
                    value = Scope.objects.get(name=value)
                except Scope.DoesNotExist:
                    return self.nodelist.render(context)
            case Scope():
                pass
            case _:
                return self.nodelist.render(context)

        with urlconf_scope(value.name), appcontext.local():
            # Render the contained template using a different current scope
            appcontext.reset()
            appcontext.set_scope(value)
            return self.nodelist.render(context)


@register.tag
def withscope(parser, token) -> WithScopeNode:
    """Parser for the {% withscope %} tag."""
    bits = token.split_contents()
    if len(bits) == 2:
        tag, value = bits
    else:
        raise TemplateSyntaxError("withscope requires exactly one argument")

    nodelist = parser.parse(("endwithscope",))
    parser.delete_first_token()

    return WithScopeNode(parser.compile_filter(value), nodelist)
