"""
Flask integration for ActingWeb applications.

Automatically generates Flask routes and handles request/response transformation.
"""

import logging
import re
from typing import TYPE_CHECKING, Any

from flask import Flask, Response, redirect, render_template, request
from werkzeug.wrappers import Response as WerkzeugResponse

from ... import request_context
from ...aw_web_request import AWWebObj
from ...handlers import bot, factory, mcp, services
from .base_integration import BaseActingWebIntegration

if TYPE_CHECKING:
    from ..app import ActingWebApp

logger = logging.getLogger(__name__)


class FlaskIntegration(BaseActingWebIntegration):
    """
    Flask integration for ActingWeb applications.

    Automatically sets up all ActingWeb routes and handles request/response
    transformation between Flask and ActingWeb.
    """

    def __init__(self, aw_app: "ActingWebApp", flask_app: Flask):
        super().__init__(aw_app)
        self.flask_app = flask_app
        self._setup_context_hooks()

    def _setup_context_hooks(self) -> None:
        """
        Setup request context management hooks.

        This configures Flask's before_request and after_request hooks to
        automatically manage request context (request ID, actor ID) for logging
        correlation.
        """

        @self.flask_app.before_request
        def _before_request() -> None:  # pyright: ignore[reportUnusedFunction]
            """Set up request context at the start of each request."""
            # Get or generate request ID from header
            req_id = request.headers.get("X-Request-ID")

            # Extract actor_id from URL path if present
            # Only matches patterns like: /<actor_id>/...  (must have path after actor)
            actor_id = None
            path = request.path
            if path and len(path) > 1:
                # Match actor_id only when followed by another path segment
                match = re.match(r"^/([^/]+)/.+", path)
                if match:
                    potential_actor = match.group(1)
                    # Skip known non-actor paths
                    if potential_actor not in [
                        "oauth",
                        "static",
                        "health",
                        "favicon.ico",
                    ]:
                        actor_id = potential_actor

            # Set context (generates request ID if not provided)
            request_context.set_request_context(
                request_id=req_id, actor_id=actor_id, generate_id=True
            )

        @self.flask_app.after_request
        def _after_request(
            response: Response | WerkzeugResponse,
        ) -> Response | WerkzeugResponse:  # pyright: ignore[reportUnusedFunction]
            """Add request ID to response headers and clear context."""
            # Add request ID to response headers for client correlation
            req_id = request_context.get_request_id()
            if req_id:
                response.headers["X-Request-ID"] = req_id

            # Clear context to prevent leakage between requests
            request_context.clear_request_context()

            return response

    def setup_routes(self) -> None:
        """Setup all ActingWeb routes in Flask."""

        # Root factory route with OAuth2 authentication
        @self.flask_app.route("/", methods=["GET"])
        def app_root_get() -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            # GET requests don't require authentication - show email form
            return self._handle_factory_get_request()

        @self.flask_app.route("/", methods=["POST"])
        def app_root_post() -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            # Check if this is a JSON API request or web form request
            is_json_request = (
                request.content_type and "application/json" in request.content_type
            )
            accepts_json = (
                request.headers.get("Accept", "").find("application/json") >= 0
            )

            if is_json_request or accepts_json:
                # Handle JSON API requests with the standard factory handler
                return self._handle_factory_request()
            else:
                # For web form requests, extract email and redirect to OAuth2 with email hint
                return self._handle_factory_post_with_oauth_redirect()

        # OAuth2 callback - handles both ActingWeb and MCP OAuth2 flows
        @self.flask_app.route("/oauth/callback", methods=["GET"])
        def app_oauth2_callback() -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            # Handle both Google OAuth2 callback (for ActingWeb) and MCP OAuth2 callback
            # Determine which flow based on state parameter
            from flask import request

            state = request.args.get("state", "")

            # Check if this is an MCP OAuth2 callback (encrypted state)
            try:
                from ...oauth2_server.state_manager import get_oauth2_state_manager

                state_manager = get_oauth2_state_manager(self.aw_app.get_config())
                mcp_context = state_manager.extract_mcp_context(state)

                if mcp_context:
                    # This is an MCP OAuth2 callback
                    return self._handle_oauth2_endpoint("callback")
            except Exception:
                # Not an MCP callback or state manager not available
                pass

            # Default to Google OAuth2 callback for ActingWeb
            return self._handle_oauth2_callback()

        # OAuth2 email input - handles email collection when OAuth provider doesn't provide one
        @self.flask_app.route("/oauth/email", methods=["GET", "POST"])
        def app_oauth2_email() -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            return self._handle_oauth2_email()

        # Email verification endpoint - verifies email addresses for OAuth2 actors
        @self.flask_app.route("/<actor_id>/www/verify_email", methods=["GET", "POST"])
        def email_verification(actor_id: str) -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            return self._handle_email_verification(actor_id)

        # OAuth2 server endpoints for MCP clients
        @self.flask_app.route("/oauth/register", methods=["POST", "OPTIONS"])
        def oauth2_register() -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            return self._handle_oauth2_endpoint("register")

        @self.flask_app.route("/oauth/authorize", methods=["GET", "POST", "OPTIONS"])
        def oauth2_authorize() -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            return self._handle_oauth2_endpoint("authorize")

        @self.flask_app.route("/oauth/token", methods=["POST", "OPTIONS"])
        def oauth2_token() -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            return self._handle_oauth2_endpoint("token")

        @self.flask_app.route("/oauth/logout", methods=["GET", "POST", "OPTIONS"])
        def oauth2_logout() -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            return self._handle_oauth2_endpoint("logout")

        # Unified OAuth endpoints (JSON API, accessible at /oauth/*)
        @self.flask_app.route("/oauth/config", methods=["GET", "OPTIONS"])
        def oauth2_config() -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            return self._handle_oauth2_spa_endpoint("config")

        @self.flask_app.route("/oauth/revoke", methods=["POST", "OPTIONS"])
        def oauth2_revoke() -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            return self._handle_oauth2_spa_endpoint("revoke")

        @self.flask_app.route("/oauth/session", methods=["GET", "OPTIONS"])
        def oauth2_session() -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            return self._handle_oauth2_spa_endpoint("session")

        # SPA-specific OAuth endpoints (different purpose than MCP OAuth2)
        @self.flask_app.route("/oauth/spa/authorize", methods=["POST", "OPTIONS"])
        def oauth2_spa_authorize() -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            return self._handle_oauth2_spa_endpoint("authorize")

        @self.flask_app.route("/oauth/spa/token", methods=["POST", "OPTIONS"])
        def oauth2_spa_token() -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            return self._handle_oauth2_spa_endpoint("token")

        # Backward compatibility routes (redirect to unified endpoints)
        @self.flask_app.route("/oauth/spa/config", methods=["GET", "OPTIONS"])
        def oauth2_spa_config_compat() -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            return self._handle_oauth2_spa_endpoint("config")

        @self.flask_app.route("/oauth/spa/callback", methods=["GET", "OPTIONS"])
        def oauth2_spa_callback() -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            return self._handle_oauth2_spa_endpoint("callback")

        @self.flask_app.route("/oauth/spa/revoke", methods=["POST", "OPTIONS"])
        def oauth2_spa_revoke_compat() -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            return self._handle_oauth2_spa_endpoint("revoke")

        @self.flask_app.route("/oauth/spa/session", methods=["GET", "OPTIONS"])
        def oauth2_spa_session_compat() -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            return self._handle_oauth2_spa_endpoint("session")

        @self.flask_app.route("/oauth/spa/logout", methods=["POST", "OPTIONS"])
        def oauth2_spa_logout() -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            # Delegate to main logout handler for consistency
            return self._handle_oauth2_endpoint("logout")

        # Bot endpoint
        @self.flask_app.route("/bot", methods=["POST"])
        def app_bot() -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            return self._handle_bot_request()

        # MCP endpoint
        @self.flask_app.route("/mcp", methods=["GET", "POST"])
        def app_mcp() -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            # For MCP, allow initial handshake without authentication
            # Authentication will be handled within the MCP protocol
            return self._handle_mcp_request()

        # OAuth2 Discovery endpoints using OAuth2EndpointsHandler
        @self.flask_app.route(
            "/.well-known/oauth-authorization-server", methods=["GET", "OPTIONS"]
        )
        def oauth_discovery() -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            """OAuth2 Authorization Server Discovery endpoint (RFC 8414)."""
            return self._handle_oauth2_discovery_endpoint(
                ".well-known/oauth-authorization-server"
            )

        @self.flask_app.route(
            "/.well-known/oauth-protected-resource", methods=["GET", "OPTIONS"]
        )
        def oauth_protected_resource_discovery() -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            """OAuth2 Protected Resource discovery endpoint."""
            return self._handle_oauth2_discovery_endpoint(
                ".well-known/oauth-protected-resource"
            )

        @self.flask_app.route(
            "/.well-known/oauth-protected-resource/mcp", methods=["GET", "OPTIONS"]
        )
        def oauth_protected_resource_mcp_discovery() -> (
            Response | WerkzeugResponse | str
        ):  # pyright: ignore[reportUnusedFunction]
            """OAuth2 Protected Resource discovery endpoint for MCP."""
            return self._handle_oauth2_discovery_endpoint(
                ".well-known/oauth-protected-resource/mcp"
            )

        # MCP information endpoint
        @self.flask_app.route("/mcp/info", methods=["GET"])
        def mcp_info() -> dict[str, Any]:  # pyright: ignore[reportUnusedFunction]
            """MCP information endpoint."""
            return self._create_mcp_info_response()

        # Actor root
        @self.flask_app.route("/<actor_id>", methods=["GET", "POST", "DELETE"])
        def app_actor_root(actor_id: str) -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            # For browser requests (Accept: text/html), redirect to /login if not authenticated
            # This provides a consistent login experience instead of going directly to OAuth
            accept_header = request.headers.get("Accept", "")
            if "text/html" in accept_header:
                # Check if user is authenticated
                auth_header = request.headers.get("Authorization")
                oauth_cookie = request.cookies.get("oauth_token")

                if not auth_header and not oauth_cookie:
                    # Unauthenticated browser - redirect to login page
                    config = self.aw_app.get_config()
                    return redirect(f"{config.root}login")

            # For API requests or authenticated browsers, use normal auth flow
            auth_redirect = self._check_authentication_and_redirect()
            if auth_redirect:
                return auth_redirect
            return self._handle_actor_request(actor_id, "root")

        # Actor meta
        @self.flask_app.route("/<actor_id>/meta", methods=["GET"])
        @self.flask_app.route("/<actor_id>/meta/<path:path>", methods=["GET"])
        def app_meta(
            actor_id: str, path: str = ""
        ) -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            return self._handle_actor_request(actor_id, "meta", path=path)

        # Actor www with OAuth2 authentication
        @self.flask_app.route("/<actor_id>/www", methods=["GET", "POST", "DELETE"])
        @self.flask_app.route(
            "/<actor_id>/www/<path:path>", methods=["GET", "POST", "DELETE"]
        )
        def app_www(actor_id: str, path: str = "") -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            # Check authentication and redirect to OAuth2 if needed
            auth_redirect = self._check_authentication_and_redirect()
            if auth_redirect:
                return auth_redirect
            return self._handle_actor_request(actor_id, "www", path=path)

        # Actor properties
        @self.flask_app.route(
            "/<actor_id>/properties", methods=["GET", "POST", "DELETE", "PUT"]
        )
        def app_properties_root(
            actor_id: str,
        ) -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            # Align with FastAPI: protect properties with OAuth when enabled
            auth_redirect = self._check_authentication_and_redirect()
            if auth_redirect:
                return auth_redirect
            return self._handle_actor_request(actor_id, "properties", name="")

        # Property metadata endpoint (must come before catch-all path:name)
        @self.flask_app.route(
            "/<actor_id>/properties/<name>/metadata",
            methods=["GET", "PUT"],
        )
        def app_property_metadata(
            actor_id: str, name: str
        ) -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            auth_redirect = self._check_authentication_and_redirect()
            if auth_redirect:
                return auth_redirect
            return self._handle_actor_request(
                actor_id, "properties", name=name, metadata=True
            )

        @self.flask_app.route(
            "/<actor_id>/properties/<path:name>",
            methods=["GET", "POST", "DELETE", "PUT"],
        )
        def app_properties(
            actor_id: str, name: str = ""
        ) -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            # Align with FastAPI: protect properties with OAuth when enabled
            auth_redirect = self._check_authentication_and_redirect()
            if auth_redirect:
                return auth_redirect
            return self._handle_actor_request(actor_id, "properties", name=name)

        # Actor trust
        @self.flask_app.route(
            "/<actor_id>/trust", methods=["GET", "POST", "DELETE", "PUT"]
        )
        @self.flask_app.route(
            "/<actor_id>/trust/<relationship>", methods=["GET", "POST", "DELETE", "PUT"]
        )
        @self.flask_app.route(
            "/<actor_id>/trust/<relationship>/<peerid>",
            methods=["GET", "POST", "DELETE", "PUT"],
        )
        def app_trust(  # pyright: ignore[reportUnusedFunction]
            actor_id: str, relationship: str | None = None, peerid: str | None = None
        ) -> Response | WerkzeugResponse | str:
            return self._handle_actor_request(
                actor_id, "trust", relationship=relationship, peerid=peerid
            )

        # Permission query endpoint - allows peers to query what permissions they've been granted
        @self.flask_app.route("/<actor_id>/permissions/<peer_id>", methods=["GET"])
        def app_permissions_query(  # pyright: ignore[reportUnusedFunction]
            actor_id: str, peer_id: str
        ) -> Response | WerkzeugResponse | str:
            return self._handle_actor_request(actor_id, "permissions", peer_id=peer_id)

        # Trust permission management endpoints
        @self.flask_app.route(
            "/<actor_id>/trust/<relationship>/<peerid>/permissions",
            methods=["GET", "PUT", "DELETE"],
        )
        def app_trust_permissions(  # pyright: ignore[reportUnusedFunction]
            actor_id: str, relationship: str, peerid: str
        ) -> Response | WerkzeugResponse | str:
            return self._handle_actor_request(
                actor_id,
                "trust",
                relationship=relationship,
                peerid=peerid,
                permissions=True,
            )

        # Trust shared properties endpoint
        @self.flask_app.route(
            "/<actor_id>/trust/<relationship>/<peerid>/shared_properties",
            methods=["GET"],
        )
        def app_trust_shared_properties(  # pyright: ignore[reportUnusedFunction]
            actor_id: str, relationship: str, peerid: str
        ) -> Response | WerkzeugResponse | str:
            return self._handle_actor_request(
                actor_id,
                "trust",
                relationship=relationship,
                peerid=peerid,
                shared_properties=True,
            )

        # Actor subscriptions
        @self.flask_app.route(
            "/<actor_id>/subscriptions", methods=["GET", "POST", "DELETE", "PUT"]
        )
        @self.flask_app.route(
            "/<actor_id>/subscriptions/<peerid>",
            methods=["GET", "POST", "DELETE", "PUT"],
        )
        @self.flask_app.route(
            "/<actor_id>/subscriptions/<peerid>/<subid>",
            methods=["GET", "POST", "DELETE", "PUT"],
        )
        @self.flask_app.route(
            "/<actor_id>/subscriptions/<peerid>/<subid>/<int:seqnr>", methods=["GET"]
        )
        def app_subscriptions(  # pyright: ignore[reportUnusedFunction]
            actor_id: str,
            peerid: str | None = None,
            subid: str | None = None,
            seqnr: int | None = None,
        ) -> Response | WerkzeugResponse | str:
            return self._handle_actor_request(
                actor_id, "subscriptions", peerid=peerid, subid=subid, seqnr=seqnr
            )

        # Actor resources
        @self.flask_app.route(
            "/<actor_id>/resources", methods=["GET", "POST", "DELETE", "PUT"]
        )
        @self.flask_app.route(
            "/<actor_id>/resources/<path:name>",
            methods=["GET", "POST", "DELETE", "PUT"],
        )
        def app_resources(
            actor_id: str, name: str = ""
        ) -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            return self._handle_actor_request(actor_id, "resources", name=name)

        # Actor callbacks
        @self.flask_app.route(
            "/<actor_id>/callbacks", methods=["GET", "POST", "DELETE", "PUT"]
        )
        @self.flask_app.route(
            "/<actor_id>/callbacks/<path:name>",
            methods=["GET", "POST", "DELETE", "PUT"],
        )
        def app_callbacks(
            actor_id: str, name: str = ""
        ) -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            return self._handle_actor_request(actor_id, "callbacks", name=name)

        # Actor devtest
        @self.flask_app.route(
            "/<actor_id>/devtest", methods=["GET", "POST", "DELETE", "PUT"]
        )
        @self.flask_app.route(
            "/<actor_id>/devtest/<path:path>", methods=["GET", "POST", "DELETE", "PUT"]
        )
        def app_devtest(
            actor_id: str, path: str = ""
        ) -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            return self._handle_actor_request(actor_id, "devtest", path=path)

        # Actor methods
        @self.flask_app.route(
            "/<actor_id>/methods", methods=["GET", "POST", "DELETE", "PUT"]
        )
        @self.flask_app.route(
            "/<actor_id>/methods/<path:name>", methods=["GET", "POST", "DELETE", "PUT"]
        )
        def app_methods(
            actor_id: str, name: str = ""
        ) -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            return self._handle_actor_request(actor_id, "methods", name=name)

        # Actor actions
        @self.flask_app.route(
            "/<actor_id>/actions", methods=["GET", "POST", "DELETE", "PUT"]
        )
        @self.flask_app.route(
            "/<actor_id>/actions/<path:name>", methods=["GET", "POST", "DELETE", "PUT"]
        )
        def app_actions(
            actor_id: str, name: str = ""
        ) -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            return self._handle_actor_request(actor_id, "actions", name=name)

        # Third-party service OAuth2 callbacks and management
        @self.flask_app.route(
            "/<actor_id>/services/<service_name>/callback", methods=["GET"]
        )
        def app_services_callback(
            actor_id: str, service_name: str
        ) -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            return self._handle_actor_request(
                actor_id,
                "services",
                name=service_name,
                code=request.args.get("code"),
                state=request.args.get("state"),
                error=request.args.get("error"),
            )

        @self.flask_app.route("/<actor_id>/services/<service_name>", methods=["DELETE"])
        def app_services_revoke(
            actor_id: str, service_name: str
        ) -> Response | WerkzeugResponse | str:  # pyright: ignore[reportUnusedFunction]
            return self._handle_actor_request(actor_id, "services", name=service_name)

    def _normalize_request(self) -> dict[str, Any]:
        """Convert Flask request to ActingWeb format."""
        cookies = {}
        raw_cookies = request.headers.get("Cookie")
        if raw_cookies:
            for cookie in raw_cookies.split("; "):
                if "=" in cookie:
                    name, value = cookie.split("=", 1)
                    cookies[name] = value

        headers = {}
        for k, v in request.headers.items():
            headers[k] = v

        # If no Authorization header but we have an oauth_token cookie (web UI session),
        # provide it as a Bearer token so core auth can validate OAuth2 and authorize creator actions.
        if "Authorization" not in headers and cookies.get("oauth_token"):
            headers["Authorization"] = f"Bearer {cookies['oauth_token']}"

        params = {}
        for k, v in request.values.items():
            params[k] = v

        # Handle form data: Flask parses form-encoded bodies into request.form,
        # leaving request.data empty. Reconstruct the form-encoded body for OAuth2 handlers.
        data = request.data
        if not data and request.form:
            from urllib.parse import urlencode

            data = urlencode(request.form).encode("utf-8")

        return {
            "method": request.method,
            "path": request.path,
            "data": data,
            "headers": headers,
            "cookies": cookies,
            "values": params,
            "url": request.url,
        }

    def _create_flask_response(
        self, webobj: AWWebObj
    ) -> Response | WerkzeugResponse | str:
        """Convert ActingWeb response to Flask response."""
        if webobj.response.redirect:
            response = redirect(webobj.response.redirect, code=302)
        else:
            response = Response(
                response=webobj.response.body,
                status=webobj.response.status_message,
                headers=webobj.response.headers,
            )

        response.status_code = webobj.response.status_code

        # Set cookies
        for cookie in webobj.response.cookies:
            response.set_cookie(
                cookie["name"],
                cookie["value"],
                max_age=cookie.get("max_age"),
                path=cookie.get("path", "/"),
                secure=cookie.get("secure", False),
                httponly=cookie.get("httponly", False),
                samesite=cookie.get("samesite", "Lax"),
            )

        return response

    def _handle_factory_request(self) -> Response | WerkzeugResponse | str:
        """Handle factory requests (actor creation)."""
        req_data = self._normalize_request()
        webobj = AWWebObj(
            url=req_data["url"],
            params=req_data["values"],
            body=req_data["data"],
            headers=req_data["headers"],
            cookies=req_data["cookies"],
        )

        # Check if user is already authenticated with OAuth2 and redirect to their actor
        oauth_cookie = request.cookies.get("oauth_token")
        if oauth_cookie and request.method == "GET":
            logger.debug(
                f"Processing GET request with OAuth cookie (length {len(oauth_cookie)})"
            )
            # User has OAuth session - try to find their actor and redirect
            try:
                from ...oauth2 import create_oauth2_authenticator

                authenticator = create_oauth2_authenticator(self.aw_app.get_config())
                if authenticator.is_enabled():
                    logger.debug("OAuth2 is enabled, validating token...")
                    # Validate the token and get user info
                    user_info = authenticator.validate_token_and_get_user_info(
                        oauth_cookie
                    )
                    if user_info:
                        email = authenticator.get_email_from_user_info(
                            user_info, oauth_cookie
                        )
                        if email:
                            logger.debug(f"Token validation successful for {email}")
                            # Look up actor by email
                            actor_instance = (
                                authenticator.lookup_or_create_actor_by_email(email)
                            )
                            if actor_instance and actor_instance.id:
                                # Redirect to actor's www page
                                redirect_url = f"/{actor_instance.id}/www"
                                logger.debug(
                                    f"Redirecting authenticated user {email} to {redirect_url}"
                                )
                                return redirect(redirect_url, code=302)
                    # Token is invalid/expired - clear the cookie and redirect to new OAuth flow
                    logger.debug(
                        "OAuth token expired or invalid - clearing cookie and redirecting to OAuth"
                    )
                    original_url = request.url
                    oauth_redirect = self._create_oauth_redirect_response(
                        redirect_after_auth=original_url, clear_cookie=True
                    )
                    return oauth_redirect
                else:
                    logger.warning("OAuth2 not enabled in config")
            except Exception as e:
                logger.error(f"OAuth token validation failed in factory: {e}")
                # Token validation failed - clear cookie and redirect to fresh OAuth
                logger.debug(
                    "OAuth token validation error - clearing cookie and redirecting to OAuth"
                )
                original_url = request.url
                oauth_redirect = self._create_oauth_redirect_response(
                    redirect_after_auth=original_url, clear_cookie=True
                )
                return oauth_redirect

        # Use the standard factory handler
        handler = factory.RootFactoryHandler(
            webobj, self.aw_app.get_config(), hooks=self.aw_app.hooks
        )

        try:
            method_name = request.method.lower()
            handler_method = getattr(handler, method_name, None)
            if handler_method and callable(handler_method):
                handler_method()
            else:
                return Response(status=405)
        except Exception as e:
            logger.error(f"Error in factory handler: {e}")
            # Map common network/SSL errors to clearer status codes if handler didn't set one
            if webobj.response.status_code != 200:
                pass
            else:
                error_message = str(e).lower()
                if "ssl" in error_message or "certificate" in error_message:
                    webobj.response.set_status(
                        502, "Bad Gateway - SSL connection failed"
                    )
                elif "connection" in error_message or "timeout" in error_message:
                    webobj.response.set_status(
                        503, "Service Unavailable - Connection failed"
                    )
                else:
                    webobj.response.set_status(500, "Internal server error")
            return self._create_flask_response(webobj)

        # Handle template rendering for factory
        if request.method == "GET" and webobj.response.status_code == 200:
            try:
                return Response(
                    render_template(
                        "aw-root-factory.html", **webobj.response.template_values
                    )
                )
            except Exception:
                pass  # Fall back to default response
        elif request.method == "POST":
            # Only render templates for form submissions, not JSON requests
            is_json_request = (
                request.content_type and "application/json" in request.content_type
            )
            if not is_json_request and webobj.response.status_code in [200, 201]:
                try:
                    return Response(
                        render_template(
                            "aw-root-created.html", **webobj.response.template_values
                        )
                    )
                except Exception:
                    pass  # Fall back to default response
            elif not is_json_request and webobj.response.status_code == 400:
                try:
                    return Response(
                        render_template(
                            "aw-root-failed.html", **webobj.response.template_values
                        )
                    )
                except Exception:
                    pass  # Fall back to default response

        return self._create_flask_response(webobj)

    def _handle_factory_get_request(self) -> Response | WerkzeugResponse | str:
        """Handle GET requests to factory route - just show the email form."""
        # Simply show the factory template without any authentication
        try:
            return Response(render_template("aw-root-factory.html"))
        except Exception:
            # Fallback for when templates are not available
            return Response(
                """
                <html>
                <head><title>ActingWeb Demo</title></head>
                <body>
                    <h1>Welcome to ActingWeb Demo</h1>
                    <form action="/" method="post">
                        <label>Your Email: <input type="email" name="creator" required /></label>
                        <input type="submit" value="Create Actor" />
                    </form>
                </body>
                </html>
            """,
                mimetype="text/html",
            )

    def _handle_factory_post_with_oauth_redirect(
        self,
    ) -> Response | WerkzeugResponse | str:
        """Handle POST to factory route with OAuth2 redirect including email hint."""
        try:
            import json

            # Parse the form data to extract email
            req_data = self._normalize_request()
            email = None

            # Try to get email from JSON body first
            if req_data["data"]:
                try:
                    data = json.loads(req_data["data"])
                    email = data.get("creator") or data.get("email")
                except (json.JSONDecodeError, ValueError):
                    pass

            # Fallback to form data
            if not email:
                email = req_data["values"].get("creator") or req_data["values"].get(
                    "email"
                )

            if not email:
                # No email provided - return error or redirect back to form
                try:
                    return Response(
                        render_template(
                            "aw-root-factory.html", error="Email is required"
                        )
                    )
                except Exception:
                    return Response("Email is required", status=400)

            logger.debug(f"Factory POST with email: {email}")

            # Create OAuth2 redirect with email hint
            try:
                from ...oauth2 import create_oauth2_authenticator

                authenticator = create_oauth2_authenticator(self.aw_app.get_config())
                if authenticator.is_enabled():
                    # Create authorization URL with email hint and User-Agent
                    redirect_after_auth = (
                        request.url
                    )  # Redirect back to factory after auth
                    user_agent = request.headers.get("User-Agent", "")
                    auth_url = authenticator.create_authorization_url(
                        redirect_after_auth=redirect_after_auth,
                        email_hint=email,
                        user_agent=user_agent,
                    )

                    logger.debug(f"Redirecting to OAuth2 with email hint: {email}")
                    return redirect(auth_url)
                else:
                    logger.warning(
                        "OAuth2 not configured - falling back to standard actor creation"
                    )
                    # Fall back to standard actor creation without OAuth
                    return self._handle_factory_post_without_oauth(email)

            except Exception as e:
                logger.error(f"Error creating OAuth2 redirect: {e}")
                # Fall back to standard actor creation if OAuth2 setup fails
                logger.debug(
                    "OAuth2 setup failed - falling back to standard actor creation"
                )
                return self._handle_factory_post_without_oauth(email)

        except Exception as e:
            logger.error(f"Error in factory POST handler: {e}")
            return Response("Internal server error", status=500)

    def _handle_factory_post_without_oauth(
        self, email: str
    ) -> Response | WerkzeugResponse | str:  # pylint: disable=unused-argument
        """Handle POST to factory route without OAuth2 - standard actor creation."""
        try:
            # Always use the standard factory handler
            req_data = self._normalize_request()
            webobj = AWWebObj(
                url=req_data["url"],
                params=req_data["values"],
                body=req_data["data"],
                headers=req_data["headers"],
                cookies=req_data["cookies"],
            )

            # Use the standard factory handler
            handler = factory.RootFactoryHandler(
                webobj, self.aw_app.get_config(), hooks=self.aw_app.hooks
            )
            handler.post()

            # Handle template rendering for factory
            if webobj.response.status_code in [200, 201]:
                try:
                    return Response(
                        render_template(
                            "aw-root-created.html", **webobj.response.template_values
                        )
                    )
                except Exception:
                    pass  # Fall back to default response
            elif webobj.response.status_code == 400:
                try:
                    return Response(
                        render_template(
                            "aw-root-failed.html", **webobj.response.template_values
                        )
                    )
                except Exception:
                    pass  # Fall back to default response

            return self._create_flask_response(webobj)

        except Exception as e:
            logger.error(f"Error in standard actor creation: {e}")
            try:
                return Response(
                    render_template(
                        "aw-root-failed.html", error="Actor creation failed"
                    )
                )
            except Exception:
                return Response("Actor creation failed", status=500)

    def _handle_bot_request(self) -> Response | WerkzeugResponse | str:
        """Handle bot requests."""
        req_data = self._normalize_request()
        webobj = AWWebObj(
            url=req_data["url"],
            params=req_data["values"],
            body=req_data["data"],
            headers=req_data["headers"],
            cookies=req_data["cookies"],
        )

        handler = bot.BotHandler(
            webobj=webobj, config=self.aw_app.get_config(), hooks=self.aw_app.hooks
        )
        handler.post(path="/bot")

        return self._create_flask_response(webobj)

    def _handle_oauth2_callback(self) -> Response | WerkzeugResponse | str:
        """Handle OAuth2 callback."""
        req_data = self._normalize_request()
        webobj = AWWebObj(
            url=req_data["url"],
            params=req_data["values"],
            body=req_data["data"],
            headers=req_data["headers"],
            cookies=req_data["cookies"],
        )

        from ...handlers.oauth2_callback import OAuth2CallbackHandler

        handler = OAuth2CallbackHandler(
            webobj, self.aw_app.get_config(), hooks=self.aw_app.hooks
        )
        result = handler.get()

        # Handle OAuth2 errors with template rendering for better UX
        if (
            isinstance(result, dict)
            and result.get("error")
            and webobj.response.status_code >= 400
        ):
            if webobj.response.template_values:
                try:
                    return Response(
                        render_template(
                            "aw-root-failed.html", **webobj.response.template_values
                        )
                    )
                except Exception:
                    pass  # Fall back to default response

        return self._create_flask_response(webobj)

    def _handle_oauth2_email(self) -> Response | WerkzeugResponse | str:
        """Handle OAuth2 email input requests."""
        req_data = self._normalize_request()
        webobj = AWWebObj(
            url=req_data["url"],
            params=req_data["values"],
            body=req_data["data"],
            headers=req_data["headers"],
            cookies=req_data["cookies"],
        )

        from ...handlers.oauth_email import OAuth2EmailHandler

        handler = OAuth2EmailHandler(
            webobj, self.aw_app.get_config(), hooks=self.aw_app.hooks
        )

        if request.method == "POST":
            handler.post()
        else:
            handler.get()

        # Handle template rendering for email form
        if (
            hasattr(webobj.response, "template_values")
            and webobj.response.template_values
        ):
            try:
                # App provides aw-oauth-email.html template
                return Response(
                    render_template(
                        "aw-oauth-email.html", **webobj.response.template_values
                    )
                )
            except Exception as e:
                # Template not found - provide basic HTML form as fallback
                logger.warning(f"Template aw-oauth-email.html not found: {e}")
                session_id = webobj.response.template_values.get("session_id", "")
                error = webobj.response.template_values.get("error", "")
                provider = webobj.response.template_values.get(
                    "provider_display", "OAuth provider"
                )
                message = webobj.response.template_values.get(
                    "message",
                    f"Your {provider} account does not have a public email. Please enter your email address to continue.",
                )

                fallback_html = f"""
                <!DOCTYPE html>
                <html>
                <head>
                    <title>Enter Email - ActingWeb</title>
                    <style>
                        body {{ font-family: Arial, sans-serif; max-width: 500px; margin: 50px auto; padding: 20px; }}
                        .error {{ color: red; margin-bottom: 15px; }}
                        .message {{ margin-bottom: 20px; color: #666; }}
                        input[type="email"] {{ width: 100%; padding: 10px; margin: 10px 0; }}
                        button {{ padding: 10px 20px; background: #4285f4; color: white; border: none; cursor: pointer; }}
                        button:hover {{ background: #357ae8; }}
                    </style>
                </head>
                <body>
                    <h1>Email Required</h1>
                    <p class="message">{message}</p>
                    {f'<p class="error">{error}</p>' if error else ""}
                    <form action="/oauth/email" method="POST">
                        <input type="hidden" name="session" value="{session_id}" />
                        <label>Email Address:
                            <input type="email" name="email" required placeholder="your@email.com" />
                        </label>
                        <button type="submit">Continue</button>
                    </form>
                </body>
                </html>
                """
                return Response(fallback_html, mimetype="text/html")

        return self._create_flask_response(webobj)

    def _handle_email_verification(
        self, actor_id: str
    ) -> Response | WerkzeugResponse | str:
        """Handle email verification requests."""
        req_data = self._normalize_request()
        webobj = AWWebObj(
            url=req_data["url"],
            params=req_data["values"],
            body=req_data["data"],
            headers=req_data["headers"],
            cookies=req_data["cookies"],
        )

        from ...handlers.email_verification import EmailVerificationHandler

        handler = EmailVerificationHandler(
            webobj, self.aw_app.get_config(), hooks=self.aw_app.hooks
        )

        if request.method == "POST":
            handler.post()
        else:
            handler.get()

        # Handle template rendering for email verification
        if (
            hasattr(webobj.response, "template_values")
            and webobj.response.template_values
        ):
            try:
                # App provides aw-verify-email.html template
                return Response(
                    render_template(
                        "aw-verify-email.html", **webobj.response.template_values
                    )
                )
            except Exception as e:
                # Template not found - provide basic HTML as fallback
                logger.warning(f"Template aw-verify-email.html not found: {e}")
                status = webobj.response.template_values.get("status", "")
                webobj.response.template_values.get("message", "")
                email = webobj.response.template_values.get("email", "")
                error = webobj.response.template_values.get("error", "")

                if status == "success":
                    fallback_html = f"""
                    <!DOCTYPE html>
                    <html>
                    <head><title>Email Verified</title></head>
                    <body>
                        <h1>✓ Email Verified!</h1>
                        <p>Your email address <strong>{email}</strong> has been verified.</p>
                        <p><a href="/{actor_id}/www">Continue to Dashboard</a></p>
                    </body>
                    </html>
                    """
                elif status == "error":
                    fallback_html = f"""
                    <!DOCTYPE html>
                    <html>
                    <head><title>Verification Failed</title></head>
                    <body>
                        <h1>Verification Failed</h1>
                        <p>{error}</p>
                        <p><a href="/{actor_id}/www">Return to Dashboard</a></p>
                    </body>
                    </html>
                    """
                else:
                    fallback_html = """
                    <!DOCTYPE html>
                    <html>
                    <head><title>Email Verification</title></head>
                    <body>
                        <h1>Email Verification</h1>
                        <p>Please check your email for a verification link.</p>
                    </body>
                    </html>
                    """
                return Response(fallback_html, mimetype="text/html")

        return self._create_flask_response(webobj)

    def _handle_oauth2_endpoint(
        self, endpoint: str
    ) -> Response | WerkzeugResponse | str:
        """Handle OAuth2 endpoints (register, authorize, token)."""
        req_data = self._normalize_request()
        webobj = AWWebObj(
            url=req_data["url"],
            params=req_data["values"],
            body=req_data["data"],
            headers=req_data["headers"],
            cookies=req_data["cookies"],
        )

        from ...handlers.oauth2_endpoints import OAuth2EndpointsHandler

        handler = OAuth2EndpointsHandler(
            webobj, self.aw_app.get_config(), hooks=self.aw_app.hooks
        )

        if request.method == "POST":
            result = handler.post(endpoint)
        elif request.method == "OPTIONS":
            result = handler.options(endpoint)
        else:
            result = handler.get(endpoint)

        # Check if handler set template values (for HTML response)
        if (
            hasattr(webobj.response, "template_values")
            and webobj.response.template_values
        ):
            # This is an HTML template response
            template_name = (
                "aw-oauth-authorization-form.html"  # Default OAuth2 template
            )
            try:
                return Response(
                    render_template(template_name, **webobj.response.template_values)
                )
            except Exception as e:
                # Template not found or rendering error - fall back to JSON
                from flask import jsonify

                return jsonify(
                    {
                        "error": "template_error",
                        "error_description": f"Failed to render template: {str(e)}",
                        "template_values": webobj.response.template_values,
                    }
                )

        # Handle redirect responses (e.g., OAuth2 callbacks)
        if isinstance(result, dict) and result.get("status") == "redirect":
            redirect_url = result.get("location")
            if redirect_url:
                redirect_response = redirect(redirect_url, code=302)

                # Add CORS headers for OAuth2 redirect responses
                redirect_response.headers["Access-Control-Allow-Origin"] = "*"
                redirect_response.headers["Access-Control-Allow-Methods"] = (
                    "GET, POST, OPTIONS"
                )
                redirect_response.headers["Access-Control-Allow-Headers"] = (
                    "Authorization, Content-Type, mcp-protocol-version"
                )

                return redirect_response

        # Return the OAuth2 result as JSON with CORS headers
        from flask import jsonify

        json_response = jsonify(result)

        # Use the status code from the handler if set
        if hasattr(webobj.response, "status_code") and webobj.response.status_code:
            json_response.status_code = webobj.response.status_code

        # Merge handler headers (e.g., WWW-Authenticate) with response
        if hasattr(webobj.response, "headers"):
            for key, value in webobj.response.headers.items():
                json_response.headers[key] = value

        # Copy cookies from handler response (e.g., for logout)
        if hasattr(webobj.response, "cookies"):
            for cookie in webobj.response.cookies:
                # Extract name as positional arg (Flask expects name as first param, not kwarg)
                cookie_data = cookie.copy()
                name = cookie_data.pop("name", None)
                if name:
                    json_response.set_cookie(name, **cookie_data)
                else:
                    logger.warning("Cookie missing 'name' field, skipping")

        # Add CORS headers for OAuth2 endpoints
        # Logout needs SPA CORS (echo origin + credentials) for cookie clearing to work
        # in cross-origin scenarios. Other endpoints use wildcard CORS.
        if endpoint == "logout":
            # Headers may have different casing depending on framework
            origin = req_data["headers"].get("Origin", "") or req_data["headers"].get(
                "origin", ""
            )
            json_response.headers["Access-Control-Allow-Origin"] = (
                origin if origin else "*"
            )
            json_response.headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS"
            json_response.headers["Access-Control-Allow-Headers"] = (
                "Authorization, Content-Type, Accept"
            )
            json_response.headers["Access-Control-Allow-Credentials"] = "true"
        else:
            json_response.headers["Access-Control-Allow-Origin"] = "*"
            json_response.headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS"
            json_response.headers["Access-Control-Allow-Headers"] = (
                "Authorization, Content-Type, mcp-protocol-version"
            )
        json_response.headers["Access-Control-Max-Age"] = "86400"

        return json_response

    def _handle_oauth2_spa_endpoint(
        self, endpoint: str
    ) -> Response | WerkzeugResponse | str:
        """Handle SPA OAuth2 endpoints (config, authorize, token, revoke, session, logout)."""
        req_data = self._normalize_request()
        webobj = AWWebObj(
            url=req_data["url"],
            params=req_data["values"],
            body=req_data["data"],
            headers=req_data["headers"],
            cookies=req_data["cookies"],
        )

        from ...handlers.oauth2_spa import OAuth2SPAHandler

        handler = OAuth2SPAHandler(
            webobj, self.aw_app.get_config(), hooks=self.aw_app.hooks
        )

        if request.method == "POST":
            result = handler.post(endpoint)
        elif request.method == "OPTIONS":
            result = handler.options(endpoint)
        else:
            result = handler.get(endpoint)

        # SPA endpoints always return JSON
        from flask import jsonify

        json_response = jsonify(result)

        # Use the status code from the handler if set
        if hasattr(webobj.response, "status_code") and webobj.response.status_code:
            json_response.status_code = webobj.response.status_code

        # Copy cookies from handler response
        if hasattr(webobj.response, "cookies"):
            for cookie in webobj.response.cookies:
                # Extract name as positional arg (Flask expects name as first param, not kwarg)
                cookie_data = cookie.copy()
                name = cookie_data.pop("name", None)
                if name:
                    json_response.set_cookie(name, **cookie_data)
                else:
                    logger.warning("Cookie missing 'name' field, skipping")

        # Add CORS headers for SPA endpoints
        origin = req_data["headers"].get("Origin", "*")
        json_response.headers["Access-Control-Allow-Origin"] = origin
        json_response.headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS"
        json_response.headers["Access-Control-Allow-Headers"] = (
            "Authorization, Content-Type, Accept"
        )
        json_response.headers["Access-Control-Allow-Credentials"] = "true"
        json_response.headers["Access-Control-Max-Age"] = "86400"

        return json_response

    def _handle_oauth2_discovery_endpoint(
        self, endpoint: str
    ) -> Response | WerkzeugResponse | str:
        """Handle OAuth2 discovery endpoints that return JSON directly."""
        req_data = self._normalize_request()
        webobj = AWWebObj(
            url=req_data["url"],
            params=req_data["values"],
            body=req_data["data"],
            headers=req_data["headers"],
            cookies=req_data["cookies"],
        )

        from ...handlers.oauth2_endpoints import OAuth2EndpointsHandler

        handler = OAuth2EndpointsHandler(
            webobj, self.aw_app.get_config(), hooks=self.aw_app.hooks
        )

        if request.method == "OPTIONS":
            result = handler.options(endpoint)
        else:
            result = handler.get(endpoint)

        # Add CORS headers directly for OAuth2 discovery endpoints
        from flask import jsonify

        response = jsonify(result)
        response.headers["Access-Control-Allow-Origin"] = "*"
        response.headers["Access-Control-Allow-Methods"] = "GET, OPTIONS"
        response.headers["Access-Control-Allow-Headers"] = (
            "Authorization, Content-Type, mcp-protocol-version"
        )
        response.headers["Access-Control-Max-Age"] = "86400"

        return response

    def _handle_mcp_request(self) -> Response | WerkzeugResponse | str:
        """Handle MCP requests."""
        req_data = self._normalize_request()
        webobj = AWWebObj(
            url=req_data["url"],
            params=req_data["values"],
            body=req_data["data"],
            headers=req_data["headers"],
            cookies=req_data["cookies"],
        )

        handler = mcp.MCPHandler(
            webobj, self.aw_app.get_config(), hooks=self.aw_app.hooks
        )

        # Execute appropriate method based on request method
        if request.method == "GET":
            result = handler.get()
        elif request.method == "POST":
            import json

            # Parse JSON body for POST requests
            try:
                if webobj.request.body:
                    data = json.loads(webobj.request.body)
                else:
                    data = {}
            except (json.JSONDecodeError, ValueError):
                data = {}

            result = handler.post(data)
        else:
            return Response(status=405)

        # Create JSON response - check if handler set a different status code (e.g., 401 for auth)
        from flask import jsonify

        status_code = 200
        headers = {}

        # Check if the handler set a custom status code in the response object
        if hasattr(webobj, "response") and hasattr(webobj.response, "status_code"):
            status_code = webobj.response.status_code

        # Check if the handler set custom headers (e.g., WWW-Authenticate for OAuth2)
        if hasattr(webobj, "response") and hasattr(webobj.response, "headers"):
            headers = dict(webobj.response.headers)

        json_response = jsonify(result)
        json_response.status_code = status_code

        # Add any custom headers from the handler
        for header_name, header_value in headers.items():
            json_response.headers[header_name] = header_value

        return json_response

    def _check_authentication_and_redirect(
        self,
    ) -> Response | WerkzeugResponse | str | None:
        """Check if request is authenticated, if not return OAuth2 redirect."""
        # Check for Basic auth
        auth_header = request.headers.get("Authorization")
        if auth_header and auth_header.startswith("Basic "):
            return None  # Has basic auth, let normal flow handle it

        # Check for Bearer token
        if auth_header and auth_header.startswith("Bearer "):
            # Align with FastAPI: if a Bearer token is present, let the underlying handlers verify it.
            # This supports both OAuth2 tokens and ActingWeb trust secret tokens without forcing redirect here.
            return None

        # Check for OAuth token cookie (for session-based authentication)
        # The oauth_token cookie may contain an ActingWeb session token OR an OAuth provider token
        oauth_cookie = request.cookies.get("oauth_token")
        if oauth_cookie:
            # First, check if this is an ActingWeb session token (SPA or /www)
            try:
                from ...oauth_session import get_oauth2_session_manager

                session_manager = get_oauth2_session_manager(self.aw_app.get_config())
                token_data = session_manager.validate_access_token(oauth_cookie)
                if token_data:
                    logger.debug(
                        f"ActingWeb session token validation successful for actor {token_data.get('actor_id')}"
                    )
                    return None  # Valid ActingWeb session token
            except Exception as e:
                logger.debug(f"ActingWeb token validation failed: {e}")

            # Fall back to validating as OAuth provider token (legacy support)
            try:
                from ...oauth2 import create_oauth2_authenticator

                authenticator = create_oauth2_authenticator(self.aw_app.get_config())
                if authenticator.is_enabled():
                    user_info = authenticator.validate_token_and_get_user_info(
                        oauth_cookie
                    )
                    if user_info:
                        email = authenticator.get_email_from_user_info(
                            user_info, oauth_cookie
                        )
                        if email:
                            logger.debug(
                                f"OAuth provider token validation successful for {email}"
                            )
                            return None  # Valid OAuth provider token
                    logger.debug(
                        "OAuth cookie token is expired or invalid - will redirect to fresh OAuth"
                    )
            except Exception as e:
                logger.debug(f"OAuth provider token validation error: {e}")

        # No valid authentication - redirect to OAuth2
        original_url = request.url
        return self._create_oauth_redirect_response(
            redirect_after_auth=original_url, clear_cookie=bool(oauth_cookie)
        )

    def _create_oauth_redirect_response(
        self, redirect_after_auth: str = "", clear_cookie: bool = False
    ) -> Response | WerkzeugResponse | str:
        """Create OAuth2 redirect response."""
        try:
            config = self.aw_app.get_config()
            from ...oauth2 import create_oauth2_authenticator

            # When multiple providers are configured, redirect to the factory
            # page so the user can choose their provider.
            providers_cfg = getattr(config, "oauth_providers", {})
            if len(providers_cfg) > 1:
                login_url = f"{config.proto}{config.fqdn}/"
                resp = redirect(login_url, code=302)
                if clear_cookie:
                    resp.delete_cookie("oauth_token", path="/")
                return resp

            authenticator = create_oauth2_authenticator(config)
            if authenticator.is_enabled():
                auth_url = authenticator.create_authorization_url(
                    redirect_after_auth=redirect_after_auth
                )
                if auth_url:
                    response = redirect(auth_url, code=302)
                    if clear_cookie:
                        # Clear the expired oauth_token cookie
                        response.delete_cookie("oauth_token", path="/")
                        logger.debug("Cleared expired oauth_token cookie")
                    return response
        except Exception as e:
            logger.error(f"Error creating OAuth2 redirect: {e}")

        # Fallback to 401 if OAuth2 not configured
        response = Response("Authentication required", status=401)
        response.headers["WWW-Authenticate"] = 'Bearer realm="ActingWeb"'
        return response

    def _handle_actor_request(
        self, actor_id: str, endpoint: str, **kwargs: Any
    ) -> Response | WerkzeugResponse | str:
        """Handle actor-specific requests."""
        req_data = self._normalize_request()
        webobj = AWWebObj(
            url=req_data["url"],
            params=req_data["values"],
            body=req_data["data"],
            headers=req_data["headers"],
            cookies=req_data["cookies"],
        )

        # Get appropriate handler
        handler = self._get_handler(endpoint, webobj, actor_id, **kwargs)
        if not handler:
            return Response(status=404)

        # Execute handler method
        try:
            method_name = request.method.lower()
            handler_method = getattr(handler, method_name, None)
            if handler_method and callable(handler_method):
                # Build positional arguments based on endpoint and kwargs
                args = [actor_id]
                if endpoint == "meta":
                    # MetaHandler.get(actor_id, path) - path defaults to "" if not provided
                    args.append(kwargs.get("path", ""))
                elif endpoint == "trust":
                    if kwargs.get("relationship"):
                        args.append(kwargs["relationship"])
                    if kwargs.get("peerid"):
                        args.append(kwargs["peerid"])
                elif endpoint == "subscriptions":
                    # Different subscription handlers:
                    # SubscriptionRootHandler.get(actor_id)
                    # SubscriptionRelationshipHandler.get(actor_id, peerid)
                    # SubscriptionHandler.get(actor_id, peerid, subid)
                    # SubscriptionDiffHandler.get(actor_id, peerid, subid, seqnr)
                    if kwargs.get("peerid"):
                        args.append(kwargs["peerid"])
                    if kwargs.get("subid"):
                        args.append(kwargs["subid"])
                    if kwargs.get("seqnr"):
                        args.append(kwargs["seqnr"])
                elif endpoint == "www":
                    # WwwHandler.get(actor_id, path) - path defaults to "" if not provided
                    args.append(kwargs.get("path", ""))
                elif endpoint == "permissions":
                    # PermissionsHandler.get(actor_id, peer_id)
                    args.append(kwargs.get("peer_id", ""))
                elif endpoint == "properties":
                    # PropertiesHandler.get(actor_id, name) - name defaults to "" if not provided
                    args.append(kwargs.get("name", ""))
                elif endpoint == "callbacks":
                    # CallbacksHandler.get(actor_id, name) - name defaults to "" if not provided
                    args.append(kwargs.get("name", ""))
                elif endpoint == "resources":
                    # ResourcesHandler.get(actor_id, name) - name defaults to "" if not provided
                    args.append(kwargs.get("name", ""))
                elif endpoint == "devtest":
                    # DevtestHandler.get(actor_id, path) - path defaults to "" if not provided
                    args.append(kwargs.get("path", ""))
                elif endpoint == "methods":
                    # MethodsHandler.get(actor_id, name) - name defaults to "" if not provided
                    args.append(kwargs.get("name", ""))
                elif endpoint == "actions":
                    # ActionsHandler.get(actor_id, name) - name defaults to "" if not provided
                    args.append(kwargs.get("name", ""))
                elif endpoint == "services":
                    # ServicesHandler.get(actor_id, service_name, **kwargs) - service_name from name parameter
                    service_name = kwargs.get("name", "")
                    args.append(service_name)
                    # For services, we need to pass additional kwargs (code, state, error)
                    for key in ["code", "state", "error"]:
                        if key in kwargs:
                            kwargs[key] = kwargs[key]

                handler_method(
                    *args,
                    **{
                        k: v
                        for k, v in kwargs.items()
                        if k in ["code", "state", "error"]
                    },
                )
            else:
                return Response(status=405)
        except Exception as e:
            logger.error(f"Error in {endpoint} handler: {e}")
            # Map common network/SSL errors to clearer status codes if handler didn't set one
            if webobj.response.status_code != 200:
                pass
            else:
                error_message = str(e).lower()
                if "ssl" in error_message or "certificate" in error_message:
                    webobj.response.set_status(
                        502, "Bad Gateway - SSL connection failed"
                    )
                elif "connection" in error_message or "timeout" in error_message:
                    webobj.response.set_status(
                        503, "Service Unavailable - Connection failed"
                    )
                else:
                    webobj.response.set_status(500, "Internal server error")
            return self._create_flask_response(webobj)

        # Special handling for www endpoint templates
        if (
            endpoint == "www"
            and request.method == "GET"
            and webobj.response.status_code == 200
        ):
            path = kwargs.get("path", "")
            template_values = webobj.response.template_values or {}
            try:
                if not path:
                    return Response(
                        render_template("aw-actor-www-root.html", **template_values)
                    )
                elif path == "init":
                    return Response(
                        render_template("aw-actor-www-init.html", **template_values)
                    )
                elif path == "properties":
                    return Response(
                        render_template(
                            "aw-actor-www-properties.html", **template_values
                        )
                    )
                elif path == "property":
                    return Response(
                        render_template("aw-actor-www-property.html", **template_values)
                    )
                elif path == "trust/new":
                    return Response(
                        render_template(
                            "aw-actor-www-trust-new.html", **template_values
                        )
                    )
                elif path.startswith("properties/"):
                    # Handle individual property pages like "properties/notes", "properties/demo_version"
                    return Response(
                        render_template("aw-actor-www-property.html", **template_values)
                    )
                elif path == "trust":
                    return Response(
                        render_template("aw-actor-www-trust.html", **template_values)
                    )
                elif (
                    hasattr(webobj.response, "template_name")
                    and webobj.response.template_name
                ):
                    # Custom template from callback hook
                    return Response(
                        render_template(
                            webobj.response.template_name, **template_values
                        )
                    )
            except Exception as e:
                logger.debug(f"Template rendering failed for www/{path}: {e}")
                # Fall back to default response

        return self._create_flask_response(webobj)

    def _get_handler(
        self,
        endpoint: str,
        webobj: AWWebObj,
        actor_id: str,
        **kwargs: Any,  # pylint: disable=unused-argument
    ) -> Any | None:
        """Get the appropriate handler for an endpoint."""
        config = self.aw_app.get_config()

        # Special handling for services endpoint (not in base class)
        if endpoint == "services":
            return self._create_services_handler(webobj, config)

        # Use base class handler selection for all other endpoints
        return self.get_handler_class(endpoint, webobj, config, **kwargs)

    def _create_oauth_discovery_response(self) -> dict[str, Any]:
        """Create OAuth2 Authorization Server Discovery response (RFC 8414).

        This describes the ActingWeb OAuth2 server endpoints that MCP
        clients use.  The upstream provider info is derived from the
        default (first) configured provider.

        .. note::

            In a multi-provider deployment this response only reflects
            the default provider.  MCP clients that need to use a
            non-default provider should use the ``/oauth/authorize``
            endpoint directly with the appropriate ``provider`` state
            parameter.
        """
        config = self.aw_app.get_config()
        base_url = f"{config.proto}{config.fqdn}"

        try:
            from ...oauth2 import create_oauth2_authenticator

            auth = create_oauth2_authenticator(config)
            provider = auth.provider
            result: dict[str, Any] = {
                "issuer": base_url,
                "authorization_endpoint": provider.auth_uri,
                "token_endpoint": provider.token_uri,
                "userinfo_endpoint": provider.userinfo_uri,
                "scopes_supported": provider.scope.split() if provider.scope else [],
                "response_types_supported": ["code"],
                "grant_types_supported": ["authorization_code"],
                "subject_types_supported": ["public"],
                "token_endpoint_auth_methods_supported": [
                    "client_secret_post",
                    "client_secret_basic",
                ],
            }
            # Google-specific extras
            if provider.name == "google":
                result["jwks_uri"] = "https://www.googleapis.com/oauth2/v3/certs"
                result["id_token_signing_alg_values_supported"] = ["RS256"]
                result["code_challenge_methods_supported"] = ["S256"]
                result["grant_types_supported"] = [
                    "authorization_code",
                    "refresh_token",
                ]
            return result
        except Exception:
            return {"error": "OAuth provider not configured"}

    def _create_mcp_info_response(self) -> dict[str, Any]:
        """Create MCP information response."""
        config = self.aw_app.get_config()
        base_url = f"{config.proto}{config.fqdn}"

        return {
            "mcp_enabled": True,
            "mcp_endpoint": "/mcp",
            "authentication": {
                "type": "oauth2",
                "provider": "actingweb",
                "required_scopes": ["mcp"],
                "flow": "authorization_code",
                "auth_url": f"{base_url}/oauth/authorize",
                "token_url": f"{base_url}/oauth/token",
                "callback_url": f"{base_url}/oauth/callback",
                "registration_endpoint": f"{base_url}/oauth/register",
                "authorization_endpoint": f"{base_url}/oauth/authorize",
                "token_endpoint": f"{base_url}/oauth/token",
                "discovery_url": f"{base_url}/.well-known/oauth-authorization-server",
                "resource_discovery_url": f"{base_url}/.well-known/oauth-protected-resource",
                "enabled": True,
            },
            "supported_features": ["tools", "prompts"],
            "tools_count": 4,
            "prompts_count": 3,
            "actor_lookup": "email_based",
            "description": "ActingWeb MCP Demo - AI can interact with actors through MCP protocol using OAuth2",
        }

    def _create_services_handler(self, webobj: AWWebObj, config) -> Any:
        """Create services handler with service registry injection."""
        handler = services.ServicesHandler(webobj, config, hooks=self.aw_app.hooks)
        # Inject service registry into the handler so it can access it
        handler._service_registry = self.aw_app.get_service_registry()
        return handler
