"""
Base class to of the template renderer.

Fastlife comes with the one rendering engine
{class}`fastlife.adapters.xcomponent.renderer.XRendererFactory`.

More template engine can be registered using the configurator method
{meth}`add_renderer <fastlife.config.configurator.GenericConfigurator.add_renderer>`
"""

import abc
from collections.abc import Mapping
from typing import Any, Generic, TypeVar, get_args, get_origin

from markupsafe import Markup
from pydantic.fields import FieldInfo

from fastlife import Request, Response
from fastlife.domain.model.form import FormModel
from fastlife.domain.model.template import InlineTemplate

TInlineTemplate = TypeVar("TInlineTemplate", bound=InlineTemplate)


class AbstractTemplateRenderer(abc.ABC, Generic[TInlineTemplate]):
    """
    An object that will be initialized by an AbstractTemplateRendererFactory,
    passing the request to process.
    """

    request: Request
    """Associated request that needs a response."""

    def __init__(self, request: Request) -> None:
        self.request = request
        self.globals: dict[str, Any] = {}

    @property
    def route_prefix(self) -> str:
        """Used to buid pydantic form widget that do ajax requests."""
        return self.request.registry.settings.fastlife_route_prefix

    def render(
        self,
        template: str,
        *,
        status_code: int = 200,
        content_type: str = "text/html",
        globals: Mapping[str, Any] | None = None,
        params: TInlineTemplate,
    ) -> Response:
        """
        Render the template and build the HTTP Response.
        """
        request = self.request
        if globals:
            self.globals.update(globals)
        data = self.render_template(params)
        resp = Response(
            data, status_code=status_code, headers={"Content-Type": content_type}
        )
        resp.set_cookie(
            request.csrf_token.name,
            request.csrf_token.value,
            httponly=True,
            secure=request.url.scheme == "https",
            samesite="strict",
            max_age=60 * 15,
        )
        return resp

    @abc.abstractmethod
    def render_template(self, template: TInlineTemplate) -> str:
        """
        Render an inline template.

        :param template: the template to render.
        :return: The template rendering result.
        """

    @abc.abstractmethod
    def pydantic_form(
        self, model: FormModel[Any], *, token: str | None = None
    ) -> Markup:
        """
        Generate HTML markup to build a form from the given form model.

        :param model: the form model that will be transformed to markup.
        :param token: a token used to ensure that unique identifier are unique.
        :return: HTML Markup generated by composign fields widgets.
        """

    @abc.abstractmethod
    def pydantic_form_field(
        self,
        model: type[Any],
        *,
        name: str | None,
        token: str | None,
        removable: bool,
        field: FieldInfo | None,
    ) -> Markup:
        """
        Generate HTML for a particular field in a form.

        This function is used to generate union subtypes in Ajax requests.
        :param model: a pydantic or python builtin type that is requests to be rendered
        :param name: name for the field
        :param token: the token of the form to render unique identifiers
        :param removable: add a way let the user remove the widget
        :param field: only render this particular field for the model.
        :return: HTML Markup.
        """


class AbstractTemplateRendererFactory(abc.ABC, Generic[TInlineTemplate]):
    """
    The template render factory.
    """

    template_type: type[TInlineTemplate]

    def __init_subclass__(cls) -> None:
        super().__init_subclass__()
        for base in cls.__orig_bases__:  # type: ignore
            if get_origin(base) is AbstractTemplateRendererFactory:  # type: ignore
                (cls.template_type,) = get_args(base)

    @abc.abstractmethod
    def __call__(self, request: Request) -> AbstractTemplateRenderer[TInlineTemplate]:
        """
        While processing an HTTP Request, a renderer object is created giving
        isolated context per request.

        :param Request: the HTTP Request to process.
        :return: The renderer object that will process that request.
        """
