"""Runtime API gateway for tenant-aware SaaS request handling."""

from __future__ import annotations

import importlib
import importlib.util

from flask import Request, jsonify

from bridge_platform.auth.jwt_auth import (
    AuthenticatedIdentity,
    AuthenticationError,
    authenticate_request,
)
from bridge_platform.entitlements.service import subscription_allows_app
from bridge_platform.logging.platform_logger import get_platform_logger, log_structured
from bridge_platform.gateway.request_context import build_request_context
from bridge_platform.quotas.service import QuotaExceeded, consume
from bridge_platform.tenants.tenant_resolver import resolve_tenant


class APIGateway:
    """Resolve tenant context and dispatch requests into app handlers."""

    def __init__(self) -> None:
        self.logger = get_platform_logger("api_gateway")

    def handle(
        self,
        flask_request: Request,
        app_name: str,
        action: str,
        *,
        trusted_identity: AuthenticatedIdentity | None = None,
    ):
        """Handle one incoming request through the SaaS runtime flow."""
        try:
            identity = trusted_identity or authenticate_request(flask_request)
            tenant_data = resolve_tenant(flask_request, identity.tenant_id)
            context = build_request_context(
                flask_request,
                tenant_data,
                app_id=app_name,
                user_id=identity.subject,
            )
        except AuthenticationError as exc:
            return jsonify({"status": "error", "message": str(exc)}), 401
        except ValueError as exc:
            return jsonify({"status": "error", "message": str(exc)}), 400
        except LookupError as exc:
            return jsonify({"status": "error", "message": str(exc)}), 404
        except PermissionError as exc:
            return jsonify({"status": "error", "message": str(exc)}), 403
        except Exception as exc:
            return jsonify({"status": "error", "message": f"Gateway failure: {exc}"}), 500

        header_app_id = (flask_request.headers.get("X-App-ID") or "").strip()
        if header_app_id and header_app_id != app_name:
            return jsonify(
                {
                    "status": "error",
                    "message": f"X-App-ID mismatch: header={header_app_id} path={app_name}",
                }
            ), 400

        if identity.tenant_id != tenant_data["tenant_id"]:
            return jsonify({"status": "error", "message": "JWT tenant does not match request tenant"}), 403

        header_user_id = (flask_request.headers.get("X-User-ID") or "").strip()
        if header_user_id and header_user_id != identity.subject:
            return jsonify({"status": "error", "message": "X-User-ID does not match JWT subject"}), 403

        context["identity"] = identity
        context["user_id"] = identity.subject
        # Caller identity is derived only from the verified identity. Internal
        # capability calls carry the source manifest app; signed Bridge calls
        # are represented by the stable WordPress bridge principal.
        context["requesting_app"] = str(
            identity.claims.get("caller_app_id")
            or identity.claims.get("app_id")
            or "wordpress_bridge"
        )

        if not subscription_allows_app(identity.tenant_id, app_name):
            return jsonify(
                {
                    "status": "error",
                    "message": f"Subscription does not allow app: {app_name}",
                }
            ), 403

        if not context["app_config"].get("enabled", False):
            return jsonify({"status": "error", "message": f"App is not enabled: {app_name}"}), 403

        if trusted_identity is None:
            required_scope = f"app:{app_name}:invoke"
            if required_scope not in identity.scopes and "app:*:invoke" not in identity.scopes:
                return jsonify({"status": "error", "message": f"Missing required scope: {required_scope}"}), 403

        try:
            consume(
                tenant_id=identity.tenant_id,
                app_id=app_name,
                metric="requests",
                metadata={"action": action, "request_id": context["request_id"]},
            )
        except QuotaExceeded as exc:
            return jsonify({
                "status": "error",
                "code": "quota_exceeded",
                "message": str(exc),
                "quota": exc.decision.to_dict(),
            }), 429

        log_structured(
            self.logger,
            "gateway_request",
            request_id=context["request_id"],
            tenant_id=context["tenant"]["tenant_id"],
            app_id=context["app_id"],
            action=action,
            method=flask_request.method,
            path=flask_request.path,
            user_id=context.get("user_id"),
            remote_addr=flask_request.remote_addr,
        )
        return self.route_to_app(context, app_name, action)

    def route_to_app(self, context: dict[str, object], app_name: str, action: str):
        if importlib.util.find_spec(f"apps.{app_name}") is None:
            return jsonify({"status": "error", "message": f"Unknown app: {app_name}"}), 404

        try:
            module = importlib.import_module(f"apps.{app_name}")
        except Exception as exc:
            return jsonify({"status": "error", "message": f"Failed to load app {app_name}: {exc}"}), 500

        handler = getattr(module, "handle_request", None)
        if handler is None:
            return jsonify({"status": "error", "message": f"App does not expose handle_request: {app_name}"}), 404

        try:
            result = handler(context, action)
        except Exception as exc:
            log_structured(
                self.logger,
                "gateway_app_error",
                request_id=context.get("request_id"),
                tenant_id=(context.get("tenant") or {}).get("tenant_id") if isinstance(context.get("tenant"), dict) else None,
                app_id=app_name,
                action=action,
                message=str(exc),
            )
            return jsonify({"status": "error", "message": str(exc)}), 500

        if isinstance(result, tuple):
            payload, status_code = result
            if isinstance(payload, dict):
                return jsonify(payload), status_code
            return payload, status_code
        if isinstance(result, dict):
            return jsonify(result)
        return result
