from functools import wraps
from inspect import _ParameterKind
from inspect import Parameter
from inspect import signature
from typing import Callable

from fastapi import Request
from pydantic import BaseModel
from sqlalchemy.engine.row import Row

from ghostforge.templates import templates

# Adapted from original at https://github.com/acmpo6ou/fastapi_html_json/blob/master/html_json.py


class HtmlJson:
    def __init__(self):
        """
        Provides @html_or_json decorator, see its documentation for more info.
        :param templates_dir: directory containing jinja2 templates.
        """
        pass

    @staticmethod
    def add_request_param(wrapper: Callable, f: Callable):
        """
        Adds `request` parameter to signature of wrapper if it's not there already.
        :param f: decorated function.
        """
        sig = signature(f)
        if "request" in sig.parameters:
            return

        params = list(sig.parameters.values())
        request_param = Parameter("request", _ParameterKind.POSITIONAL_OR_KEYWORD, annotation=Request)
        params.insert(0, request_param)
        wrapper.__signature__ = sig.replace(parameters=params)

    def render_template(self, template: str, request: Request, result):
        """
        Renders jinja2 template no matter what the view function returns: be it dictionary,
        pydantic model or a list.

        :param template: path to the template.
        :param request: needed by TemplateResponse.
        :param result: return value of the view function.
        :param breadcrumbs: breadcrumbs for HTML rendering.
        :return: rendered template.
        """
        if isinstance(result, BaseModel):
            result = result.dict()
        elif isinstance(result, list):
            result = {"data": result[:]}
        elif isinstance(result, Row):
            tmp = {}
            for k in result.keys():
                tmp[k] = result[k]
            result = tmp

        if hasattr(request.state, "ghostforge"):
            for key, value in request.state.ghostforge.items():
                result[key] = value

        result.update({"request": request})
        return templates.TemplateResponse(template, result)

    def html_or_json(self, template: str):
        """
        A decorator that will make decorated async view function able to return jinja2 template
        or json depending on Accept header of request.
        :param template: path to jinja2 template.
        """

        def decorator(f: Callable):
            @wraps(f)
            async def wrapper(*args, **kwargs):
                request = kwargs["request"]

                try:
                    result = await f(*args, **kwargs)
                except TypeError:
                    kwargs.pop("request")
                    result = await f(*args, **kwargs)

                accept = request.headers["accept"].split(",")[0]
                if accept == "text/html":
                    return self.render_template(template, request, result)

                return result

            self.add_request_param(wrapper, f)
            return wrapper

        return decorator
