"""
SAYTHON Framework - Router
Supports path params: /users/<id>  /posts/<slug>
"""
import re
from typing import Callable, Optional


class Route:
    """A single registered route."""

    # Converts  /users/<id>/posts/<post_id>
    # to regex  /users/(?P<id>[^/]+)/posts/(?P<post_id>[^/]+)
    PARAM_RE = re.compile(r"<([^>]+)>")

    def __init__(self, method: str, path: str, handler: Callable, middlewares: list = None):
        self.method = method.upper()
        self.path = path
        self.handler = handler
        self.middlewares = middlewares or []

        # Build a regex pattern for the path
        pattern = self.PARAM_RE.sub(r"(?P<\1>[^/]+)", re.escape(path))
        pattern = pattern.replace(r"\/", "/")          # unescape slashes
        self._regex = re.compile(f"^{pattern}$")

    def match(self, method: str, path: str) -> Optional[dict]:
        """
        Returns path params dict if route matches, else None.
        Also returns empty dict for routes without params.
        """
        if self.method != method.upper():
            return None
        m = self._regex.match(path)
        if m:
            return m.groupdict()
        return None

    def __repr__(self):
        return f"<Route {self.method} {self.path}>"


class Router:
    """
    Collects and resolves routes.

    Usage:
        router = Router()

        @router.get("/users")
        def list_users(req): ...

        @router.post("/users")
        def create_user(req): ...

        @router.get("/users/<id>")
        def get_user(req): ...
    """

    def __init__(self):
        self._routes: list[Route] = []

    # ------------------------------------------------------------------ #
    #  Decorator helpers
    # ------------------------------------------------------------------ #
    def _add(self, method: str, path: str, middlewares: list = None):
        def decorator(fn: Callable):
            self._routes.append(Route(method, path, fn, middlewares))
            return fn
        return decorator

    def get(self, path: str, middlewares: list = None):
        return self._add("GET", path, middlewares)

    def post(self, path: str, middlewares: list = None):
        return self._add("POST", path, middlewares)

    def put(self, path: str, middlewares: list = None):
        return self._add("PUT", path, middlewares)

    def patch(self, path: str, middlewares: list = None):
        return self._add("PATCH", path, middlewares)

    def delete(self, path: str, middlewares: list = None):
        return self._add("DELETE", path, middlewares)

    def route(self, path: str, methods: list = None, middlewares: list = None):
        """Register multiple HTTP methods on one path."""
        methods = methods or ["GET"]
        def decorator(fn: Callable):
            for m in methods:
                self._routes.append(Route(m, path, fn, middlewares))
            return fn
        return decorator

    # ------------------------------------------------------------------ #
    #  Resolution
    # ------------------------------------------------------------------ #
    def resolve(self, method: str, path: str):
        """
        Returns (handler, params, middlewares) or raises NotFound / MethodNotAllowed.
        """
        from saython.exceptions import NotFound, MethodNotAllowed

        path_matched = False
        for route in self._routes:
            params = route.match(method, path)
            if params is not None:
                return route.handler, params, route.middlewares
            # Check if path matches but method doesn't (for MethodNotAllowed)
            if route._regex.match(path):
                path_matched = True

        if path_matched:
            raise MethodNotAllowed(f"Method {method} not allowed")
        raise NotFound(f"Route not found: {method} {path}")

    # ------------------------------------------------------------------ #
    #  Mount sub-router under a prefix
    # ------------------------------------------------------------------ #
    def include(self, prefix: str, router: "Router"):
        """Mount another Router's routes under a URL prefix."""
        prefix = prefix.rstrip("/")
        for route in router._routes:
            new_path = prefix + route.path
            self._routes.append(Route(route.method, new_path, route.handler, route.middlewares))

    def __repr__(self):
        return f"<Router routes={len(self._routes)}>"
