"""
Built-in Caprail middleware.

CorsMiddleware, SessionMiddleware, AuthenticateMiddleware,
RedirectIfAuthenticated, TrustProxies.
"""
from __future__ import annotations

from caprail.http.middleware import Middleware, NextCallable
from caprail.http.request import Request
from caprail.http.response import Response


class CorsMiddleware(Middleware):
    """
    Add CORS headers to every response.

    Configure by subclassing and overriding the class attributes.
    """

    allow_origins: list[str] = ["*"]
    allow_methods: list[str] = ["*"]
    allow_headers: list[str] = ["*"]
    allow_credentials: bool = False
    max_age: int = 0

    async def handle(self, request: Request, next: NextCallable) -> Response:
        if request.method == "OPTIONS":
            response = Response("", status=204)
        else:
            response = await next(request)

        origin = request.header("origin", "*")
        if "*" in self.allow_origins:
            response.with_header("access-control-allow-origin", origin)
        else:
            if origin in self.allow_origins:
                response.with_header("access-control-allow-origin", origin)

        response.with_header(
            "access-control-allow-methods",
            ", ".join(self.allow_methods) if "*" not in self.allow_methods else "*",
        )
        response.with_header(
            "access-control-allow-headers",
            ", ".join(self.allow_headers) if "*" not in self.allow_headers else "*",
        )
        if self.allow_credentials:
            response.with_header("access-control-allow-credentials", "true")
        if self.max_age:
            response.with_header("access-control-max-age", str(self.max_age))
        return response


class SessionMiddleware(Middleware):
    """
    Cookie-based session middleware.

    Reads the encrypted session cookie, populates ``request.session``,
    and writes it back in the response.
    """

    def __init__(self, secret_key: str, session_cookie: str = "caprail_session") -> None:
        self._secret = secret_key
        self._cookie_name = session_cookie

    async def handle(self, request: Request, next: NextCallable) -> Response:
        import json
        from itsdangerous import URLSafeTimedSerializer, BadSignature

        signer = URLSafeTimedSerializer(self._secret)
        raw = request.cookie(self._cookie_name)
        session: dict = {}
        if raw:
            try:
                session = signer.loads(raw, max_age=7200)
            except BadSignature:
                session = {}

        request.session = session
        response = await next(request)

        serialized = signer.dumps(request.session)
        response.with_cookie(
            self._cookie_name,
            serialized,
            path="/",
            http_only=True,
            same_site="Lax",
        )
        return response


class AuthenticateMiddleware(Middleware):
    """
    Ensure the user is authenticated.

    Redirects to /login (or returns 401 for JSON requests).
    """

    redirect_to: str = "/login"

    async def handle(self, request: Request, next: NextCallable) -> Response:
        if not request.session.get("_auth_id") and request.user is None:
            if request.wants_json():
                return Response.json({"message": "Unauthenticated."}, status=401)
            return Response.redirect(self.redirect_to)
        return await next(request)


class RedirectIfAuthenticated(Middleware):
    """Redirect authenticated users away from guest-only routes."""

    redirect_to: str = "/dashboard"

    async def handle(self, request: Request, next: NextCallable) -> Response:
        if request.session.get("_auth_id") or request.user is not None:
            return Response.redirect(self.redirect_to)
        return await next(request)


class TrustProxies(Middleware):
    """Trust X-Forwarded-* headers from proxies."""

    async def handle(self, request: Request, next: NextCallable) -> Response:
        if request.has_header("x-forwarded-proto"):
            request._scope["scheme"] = request.header("x-forwarded-proto")
        if request.has_header("x-forwarded-for"):
            ip = request.header("x-forwarded-for", "").split(",")[0].strip()
            if ip:
                request._scope["client"] = (ip, 0)
        return await next(request)
