from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Callable


@dataclass(slots=True)
class BlueprintRoute:
    rule: str
    methods: list[str] | None
    endpoint: str | None
    handler: Callable[..., Any]


class Blueprint:
    def __init__(
        self,
        name: str,
        import_name: str,
        *,
        url_prefix: str | None = None,
    ) -> None:
        self.name = name
        self.import_name = import_name
        self.url_prefix = url_prefix or ""
        self._routes: list[BlueprintRoute] = []
        self._before_request_funcs: list[Callable[..., Any]] = []
        self._after_request_funcs: list[Callable[..., Any]] = []
        self._teardown_request_funcs: list[Callable[..., Any]] = []
        self._error_handlers: dict[Any, Callable[..., Any]] = {}

    def route(
        self,
        rule: str,
        *,
        methods: list[str] | None = None,
        endpoint: str | None = None,
    ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
        def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
            self._routes.append(
                BlueprintRoute(rule=rule, methods=methods, endpoint=endpoint, handler=func)
            )
            return func

        return decorator

    def get(self, rule: str, **kwargs: Any) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
        return self.route(rule, methods=["GET"], **kwargs)

    def post(self, rule: str, **kwargs: Any) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
        return self.route(rule, methods=["POST"], **kwargs)

    def put(self, rule: str, **kwargs: Any) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
        return self.route(rule, methods=["PUT"], **kwargs)

    def patch(self, rule: str, **kwargs: Any) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
        return self.route(rule, methods=["PATCH"], **kwargs)

    def delete(self, rule: str, **kwargs: Any) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
        return self.route(rule, methods=["DELETE"], **kwargs)

    def before_request(self, func: Callable[..., Any]) -> Callable[..., Any]:
        self._before_request_funcs.append(func)
        return func

    def after_request(self, func: Callable[..., Any]) -> Callable[..., Any]:
        self._after_request_funcs.append(func)
        return func

    def teardown_request(self, func: Callable[..., Any]) -> Callable[..., Any]:
        self._teardown_request_funcs.append(func)
        return func

    def errorhandler(self, code_or_exception: Any) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
        def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
            self.register_error_handler(code_or_exception, func)
            return func

        return decorator

    def register_error_handler(self, code_or_exception: Any, func: Callable[..., Any]) -> None:
        self._error_handlers[code_or_exception] = func

    def register(self, app: Any, *, url_prefix: str | None = None) -> None:
        prefix = _join_paths(url_prefix or "", self.url_prefix)
        app._register_blueprint_hooks(
            self.name,
            before_request_funcs=self._before_request_funcs,
            after_request_funcs=self._after_request_funcs,
            teardown_request_funcs=self._teardown_request_funcs,
        )
        for key, func in self._error_handlers.items():
            app._register_blueprint_error_handler(self.name, key, func)

        for route in self._routes:
            rule = _join_paths(prefix, route.rule)
            endpoint = route.endpoint or route.handler.__name__
            app.route(
                rule,
                methods=route.methods,
                endpoint=f"{self.name}.{endpoint}",
            )(route.handler)


def _join_paths(prefix: str, path: str) -> str:
    left = prefix.strip("/")
    right = path.strip("/")
    if left and right:
        return f"/{left}/{right}"
    if left:
        return f"/{left}"
    if right:
        return f"/{right}"
    return "/"
