"""vps-manager 后端入口

- 注册资产 CRUD 与统计路由
- 挂载本地静态资源（Vue/Tailwind/前端逻辑）
- 渲染前端 SPA 入口页面
- 启用 CORS（便于外部程序/其他 AI 跨域调用写入接口）
"""

from contextlib import asynccontextmanager
from pathlib import Path
import asyncio
import logging

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, HTMLResponse
from starlette.staticfiles import StaticFiles as StarletteStaticFiles
from jinja2 import Environment, FileSystemLoader
from sqlmodel import Session

from app.core.config import settings
from app.core.seed import seed_providers
from app.database import assets_engine, init_db
from app.routers import agent, assets, monitor, notify, providers, ssl, stats, sync
from app.services import cleanup_service

BASE_DIR = Path(__file__).resolve().parent.parent
STATIC_DIR = BASE_DIR / "static"
TEMPLATE_DIR = BASE_DIR / "app" / "templates"
STATIC_DIR.mkdir(parents=True, exist_ok=True)


def _asset_version() -> str:
    """静态资源版本号：取 static 目录内最新文件 mtime，代码更新后自动变更。

    用于前端引用 ?v= 参数，绕开浏览器启发式缓存（旧响应无 Cache-Control
    时存下的条目会被视为新鲜而不再回源验证）。
    启动时计算一次并缓存：静态资源只在代码更新时变化，而更新后服务会重启，
    避免每次页面请求都遍历 stat 整个 static 目录。
    """
    global _ASSET_VERSION_CACHE
    if _ASSET_VERSION_CACHE is None:
        latest = 0
        for p in STATIC_DIR.rglob("*"):
            if p.is_file():
                latest = max(latest, int(p.stat().st_mtime))
        _ASSET_VERSION_CACHE = str(latest)
    return _ASSET_VERSION_CACHE


_ASSET_VERSION_CACHE: str | None = None

# 全局日志配置：统一格式，便于生产环境排查
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s [%(name)s] %(message)s",
)

# 直接使用 Jinja2 Environment 渲染（规避 Starlette Jinja2Templates 在 Python 3.14 下的缓存兼容问题）
jinja_env = Environment(loader=FileSystemLoader(TEMPLATE_DIR), autoescape=True)


@asynccontextmanager
async def lifespan(_: FastAPI):
    """应用启动时自动建表、初始化预设平台，并启动监控数据定期清理任务"""
    init_db()
    with Session(assets_engine) as session:
        seed_providers(session)
    cleanup_task = asyncio.create_task(cleanup_service.cleanup_loop())
    yield
    cleanup_task.cancel()


app = FastAPI(title=settings.APP_NAME, version="0.2.0", lifespan=lifespan)

app.add_middleware(
    CORSMiddleware,
    allow_origins=[o.strip() for o in settings.CORS_ORIGINS.split(",") if o.strip()],
    allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
    allow_headers=["Content-Type", "X-API-Key", "X-Agent-Key"],
    allow_credentials=True,
)

app.include_router(assets.router)
app.include_router(providers.router)
app.include_router(stats.router)
app.include_router(agent.router)
app.include_router(monitor.router)
app.include_router(sync.router)
app.include_router(notify.router)
app.include_router(ssl.router)

# 静态资源：no-cache（每次回源校验 ETag）。避免启发式缓存导致旧版本残留，
# 与 SW 的 no-store 回源配合，保证代码更新后立即生效。
class NoCacheStaticFiles(StarletteStaticFiles):
    def file_response(self, *args, **kwargs):
        resp = super().file_response(*args, **kwargs)
        resp.headers.setdefault("Cache-Control", "no-cache")
        return resp

app.mount("/static", NoCacheStaticFiles(directory=STATIC_DIR), name="static")


@app.get("/", include_in_schema=False)
def index() -> HTMLResponse:
    """前端 SPA 入口"""
    html = jinja_env.get_template("index.html").render(
        app_name=settings.APP_NAME, asset_version=_asset_version()
    )
    return HTMLResponse(html)


@app.get("/sw.js", include_in_schema=False)
def service_worker() -> FileResponse:
    """Service Worker（置于根路径以使 scope 覆盖全站）

    no-cache：保证浏览器每次导航都校验 SW 是否有更新，
    否则浏览器可能长时间持有旧版 SW（默认更新检查间隔长）。
    """
    return FileResponse(
        STATIC_DIR / "sw.js",
        media_type="application/javascript",
        headers={
            "Service-Worker-Allowed": "/",
            "Cache-Control": "no-cache",
        },
    )


@app.get("/health")
def health_check() -> dict:
    """健康检查（version 动态读取，便于验证自动更新）"""
    return {"status": "ok", "app": "vps-manager", "version": app.version}
