from collections.abc import Callable
from http import HTTPMethod

import structlog

from zayt.web.routing.exception import DuplicateRouteError
from zayt.web.routing.route import Route, RouteMatch

logger = structlog.get_logger(__name__)


class Router:
    def __init__(self):
        self.routes: list[Route] = []

    def _check_duplicates(self, route):
        for current_route in self.routes:
            if (
                current_route.action != route.action
                and current_route.method == route.method
                and current_route.regex == route.regex
            ):
                raise DuplicateRouteError(route.name, current_route.name)

    def route(
        self, handler: Callable, method: HTTPMethod, path: str, *, prefix: str = None
    ):
        path = path.strip("/")
        if prefix:
            path = prefix.strip("/") + "/" + path

        route_name = f"{method.lower()}.{handler.__module__}.{handler.__qualname__}"
        route = Route(method, path, handler, route_name)
        self._check_duplicates(route)

        self.routes.append(route)
        logger.debug(
            "action registered",
            handler=f"{handler.__module__}.{handler.__qualname__}",
            method=route.method,
            path=route.path,
        )

        self.routes.sort()

    def websocket(self, handler: Callable, path: str, *, prefix: str = None):
        path = path.strip("/")
        if prefix:
            path = prefix.strip("/") + "/" + path

        route_name = f"websocket.{handler.__module__}.{handler.__qualname__}"
        route = Route(None, path, handler, route_name)
        self._check_duplicates(route)

        self.routes.append(route)
        logger.debug(
            "websocket registered",
            handler=f"{handler.__module__}.{handler.__qualname__}",
            path=route.path,
        )

        self.routes.sort()

    def match(self, method: HTTPMethod | None, path: str) -> RouteMatch | None:
        """Match a path against the routes

        :param method: HTTP method or None for websocket
        :param path: path to match against
        """

        for route in self.routes:
            if (match := route.match(method, path)) is not None:
                return RouteMatch(route, method, path, match)

        return None
