"""<<project_name>> 启动入口 (entrypoint).

读取 config.toml / .env 中的 host、port，按其启动 uvicorn。
改监听地址/端口请改 config.toml，不要改这里。
Reads host/port from config.toml / .env and starts uvicorn accordingly.
To change the listen address/port, edit config.toml — not this file.

启动 / Run:
    uv run python main.py
"""

import contextlib
import warnings
from pathlib import Path

# 屏蔽第三方库的弃用告警（websockets via uvicorn、authlib via fastmcp），保持启动日志干净；
# 不影响本项目自身代码的告警。下面的设置顺序很关键，必须严格遵守，否则过滤器会被第三方库覆盖。
# Silence noisy THIRD-PARTY deprecation warnings only (websockets via uvicorn, authlib via
# fastmcp) so the default startup log stays clean — the app's own warnings still surface.
# The ORDER below is load-bearing; each library mutates the global warnings filter at import
# time, so we must interleave our narrow filters with their imports to land in FRONT of them.
#
# ① authlib 先行：authlib.deprecate 导入时执行一次性 simplefilter("always",
#    AuthlibDeprecationWarning)，会把自身告警插到过滤器最前。先导入它一次“用掉”这次
#    simplefilter（模块随后被缓存不再重复），再以精确类别 + message 把它静音。fastmcp 在导入时会
#    `from authlib.jose import ...` 触发该告警，所以必须在 import fastmcp 之前完成 authlib 静音。
#    official backend 无 authlib，导入失败跳过即可。
# ① authlib first: importing authlib.deprecate runs a one-shot simplefilter("always",
#    AuthlibDeprecationWarning) that jams its warning to the FRONT of the filter list. Import it
#    once to SPEND that one-shot (the module is then cached and never re-runs), then silence it by
#    exact category + message. fastmcp triggers `from authlib.jose import ...` at import time, so
#    authlib MUST be silenced before `import fastmcp`. The official backend has no authlib — skip.
with contextlib.suppress(ImportError):
    from authlib.deprecate import AuthlibDeprecationWarning

    warnings.filterwarnings("ignore", category=AuthlibDeprecationWarning)
warnings.filterwarnings("ignore", message=r".*authlib\.jose.*", category=DeprecationWarning)

# ② 触发 fastmcp 的导入副作用：fastmcp/__init__.py 执行 simplefilter("default",
#    DeprecationWarning)，把一条“匹配所有 DeprecationWarning、无模块限制”的过滤器插到最前。若我们的
#    窄过滤器在它之前设置，会被这条 catch-all 抢先命中（首个匹配生效），导致 websockets/uvicorn 告警
#    照常打印。因此先 import fastmcp 让它“用掉”这次 simplefilter，再在 ③ 设置我们的窄过滤器，
#    使其位于 catch-all 之前。official backend 不依赖 fastmcp，导入失败跳过即可。
# ② Trigger fastmcp's import side effect: fastmcp/__init__.py runs
#    simplefilter("default", DeprecationWarning), inserting a catch-all (matches EVERY
#    DeprecationWarning, no module restriction) at the FRONT of the filter list. If our narrow
#    filters were set before it, that catch-all would win first-match and the websockets/uvicorn
#    warnings would still print. So import fastmcp first to let it run its simplefilter, THEN set
#    our narrow filters in ③ so they sit in front of the catch-all. Official backend lacks fastmcp.
with contextlib.suppress(ImportError):
    import fastmcp  # noqa: F401

# ③ 现在设置窄范围（仅第三方模块）过滤器，使其位于上述 catch-all 之前；这些告警在 uvicorn 启动时
#    懒加载 websockets 协议实现才触发，此处需先于该懒加载落位。绝不做全局屏蔽，
#    本项目自身告警仍可见。
# ③ Now set the narrow (third-party-module-only) filters so they precede the catch-all above. These
#    warnings fire when uvicorn lazily imports the websockets protocol impl at server startup, so
#    the filters must be in place before that. NEVER a blanket ignore — the app's own warnings show.
warnings.filterwarnings("ignore", category=DeprecationWarning, module=r"websockets.*")
warnings.filterwarnings("ignore", category=DeprecationWarning, module=r"uvicorn.*")

import uvicorn  # noqa: E402

from app.logging import init_logging  # noqa: E402
from app.settings import settings  # noqa: E402

BASE_DIR = Path(__file__).resolve().parent

# 热重载只监听源码目录 app/，并把入口/配置文件显式纳入。
# 关键：日志写入 logs/ 在项目根目录内，若监听整个根目录会被日志写入反复触发
# reload，形成死循环。这里把 logs/、虚拟环境、缓存、git 等会变动但非源码的路径排除。
# Only watch the app/ source dir for reload, and explicitly include the entry /
# config files. The logger writes into logs/ inside the project root; watching the
# whole root makes each log write re-trigger reload, an infinite loop. Exclude
# logs/, the venv, caches and git so they never re-trigger the reloader.
_RELOAD_DIRS = [str(BASE_DIR / "app")]
_RELOAD_INCLUDES = ["main.py", "config.toml", ".env"]
_RELOAD_EXCLUDES = [
    "logs",
    "logs/*",
    "*.log",
    "*.log.*",
    ".venv",
    ".venv/*",
    "__pycache__",
    "__pycache__/*",
    "*.pyc",
    ".git",
    ".git/*",
]


def main() -> None:
    """按配置启动 MCP HTTP 服务."""
    init_logging(settings.log)
    # reload 完全由 settings.server.reload 决定，默认 false（见 config.toml）。
    # 生产用 `make run` / `uv run python main.py` 运行，reload 关闭。
    # 唯一开启方式：本地开发 `make dev`（它设 SERVER__RELOAD=true 覆盖配置）。
    # 此处没有任何强制 reload=True 的代码路径。
    # reload is driven solely by settings.server.reload (default false; see config.toml).
    # Production runs `make run` / `uv run python main.py` with reload OFF.
    # The ONLY intended way to enable it is local dev via `make dev`
    # (which sets SERVER__RELOAD=true to override the config). No code path forces it on.
    run_kwargs: dict[str, object] = {
        "host": settings.server.host,
        "port": settings.server.port,
        "reload": settings.server.reload,
        # 禁用 uvicorn 自带的 dictConfig，使其 uvicorn/uvicorn.error/uvicorn.access logger
        # 老实 propagate 到已被 init_logging 配好的 structlog root handler；否则 uvicorn.run()
        # 会重新应用自己的默认 logging 配置，覆盖 init_logging 的 clear()+propagate 设置，
        # 打印裸 "INFO:     ..." 行与 structlog 格式混杂。
        # Disable uvicorn's built-in dictConfig so its uvicorn/uvicorn.error/uvicorn.access
        # loggers propagate to the structlog root handler already configured by init_logging;
        # otherwise uvicorn.run() re-applies its own default logging config, overriding
        # init_logging's clear()+propagate and printing bare "INFO:     ..." lines.
        "log_config": None,
    }
    if settings.server.reload:
        # reload 调优参数仅在开启 reload 时有意义；reload 关闭时若仍传入，
        # uvicorn 会打印 "Current configuration will not reload" 告警。
        # Reload-tuning only matters when reload is enabled; passing these while
        # reload is off makes uvicorn warn "configuration will not reload".
        run_kwargs["reload_dirs"] = _RELOAD_DIRS
        run_kwargs["reload_includes"] = _RELOAD_INCLUDES
        run_kwargs["reload_excludes"] = _RELOAD_EXCLUDES

    uvicorn.run("app.server:asgi_app", **run_kwargs)


if __name__ == "__main__":
    main()
