"""server-core 统一入口（dispatcher）—— 对外生产服务 opc.pinesound.cn 单入口。

按「端口专属前缀」把请求分流到对应子应用，每端口自带前缀子路由、互不冲突：
  /park/*                                  → 园区子应用（app.park，大屏/MQTT/园区数据）
  /api/*、/uploads/*、/SpXvScDiDT.txt     → 培训子应用（app.training，独立 data/opc.db）
  /auth /opc /admin /government /investor …→ 平台应用（app.main，身份 + 业务域，各端口子路由）

启动（server-core 目录）：
    uv run uvicorn dispatcher:app --host 0.0.0.0 --port 8090 --reload
"""
from __future__ import annotations

import logging

from app.main import app as core_app
from app.park.app import app as park_app
from app.training.main import app as training_app

logger = logging.getLogger(__name__)

ROUTE_TRAINING_PREFIXES = ("/api", "/uploads", "/SpXvScDiDT.txt")
ROUTE_PARK_PREFIX = "/park"


def _prefix_app(path: str):
    """按路径前缀匹配目标子应用；未命中返回 None 交由平台应用（app.main）。"""
    if path.startswith(ROUTE_PARK_PREFIX):
        return park_app
    if path.startswith(ROUTE_TRAINING_PREFIXES):
        return training_app
    return None


class Dispatcher:
    """极简 ASGI 分发器：驱动子应用 lifespan（核心库初始化）+ 按路径路由。"""

    def __init__(self, apps: list):
        self._apps = apps

    async def _run_lifespan(self, receive, send):
        """依次进入各子应用 lifespan（核心应用负责建表/种子），按启动/停止消息配对退出。"""
        started: list = []
        try:
            while True:
                message = await receive()
                if message["type"] == "lifespan.startup":
                    for a in self._apps:
                        cm = a.router.lifespan_context(app=a)
                        await cm.__aenter__()
                        started.append(cm)
                    await send({"type": "lifespan.startup.complete"})
                elif message["type"] == "lifespan.shutdown":
                    for cm in reversed(started):
                        await cm.__aexit__(None, None, None)
                    started = []
                    await send({"type": "lifespan.shutdown.complete"})
                    return
        except BaseException:
            for cm in reversed(started):
                try:
                    await cm.__aexit__(None, None, None)
                except Exception:  # noqa: BLE001
                    pass
            raise

    async def __call__(self, scope, receive, send):
        if scope["type"] == "lifespan":
            await self._run_lifespan(receive, send)
            return
        path = scope.get("path", "")
        target = _prefix_app(path) or core_app
        await target(scope, receive, send)


app = Dispatcher([core_app, training_app, park_app])
