"""FastAPI 入口。

通过 `uvicorn app.main:app --reload` 启动。
"""

from __future__ import annotations

import logging
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from pathlib import Path

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles

from app.api.internal.app_version import router as internal_app_version_router
from app.api.internal.launch_confirm import router as internal_launch_confirm_router
from app.api.internal.price import router as internal_price_router
from app.api.internal.store import router as internal_store_router
from app.api.v1.ad import router as ad_router
from app.api.v1.analytics import router as analytics_router
from app.api.v1.applog import router as applog_router
from app.api.v1.auth import router as auth_router
from app.api.v1.compare import router as compare_router
from app.api.v1.compare_milestone import router as compare_milestone_router
from app.api.v1.compare_record import router as compare_record_router
from app.api.v1.coupon import router as coupon_router
from app.api.v1.cps_redirect import router as cps_redirect_router
from app.api.v1.device import router as device_router
from app.api.v1.feedback import router as feedback_router
from app.api.v1.guide_video import router as guide_video_router
from app.api.v1.invite import router as invite_router
from app.api.v1.meituan import router as meituan_router
from app.api.v1.notifications import router as notifications_router
from app.api.v1.order import router as order_router
from app.api.v1.platform import router as platform_router
from app.api.v1.push import router as push_router
from app.api.v1.report import router as report_router
from app.api.v1.savings import router as savings_router
from app.api.v1.signin import router as signin_router
from app.api.v1.tasks import router as tasks_router
from app.api.v1.user import router as user_router
from app.api.v1.wallet import router as wallet_router
from app.api.v1.wxpay import router as wxpay_router
from app.core.config import settings
from app.core.cps_reconcile_worker import (
    start_cps_reconcile_worker,
    stop_cps_reconcile_worker,
)
from app.core.daily_exchange_worker import (
    start_daily_exchange_worker,
    stop_daily_exchange_worker,
)
from app.core.heartbeat_monitor_worker import (
    start_heartbeat_monitor,
    stop_heartbeat_monitor,
)
from app.core.inactivity_reset_worker import (
    start_inactivity_reset_worker,
    stop_inactivity_reset_worker,
)
from app.core.llm_cost_backfill_worker import (
    start_llm_cost_backfill_worker,
    stop_llm_cost_backfill_worker,
)
from app.core.logging import setup_logging
from app.core.observe import RequestMetricsMiddleware
from app.core.observe_worker import (
    start_observe_worker,
    stop_observe_worker,
)
from app.core.pricebot_client import aclose_pricebot_client, get_pricebot_client
from app.core.withdraw_reconcile_worker import (
    start_withdraw_reconcile_worker,
    stop_withdraw_reconcile_worker,
)
from app.integrations import meituan as mt_meituan

setup_logging(debug=settings.APP_DEBUG)
logger = logging.getLogger("shagua.main")


@asynccontextmanager
async def lifespan(_: FastAPI) -> AsyncIterator[None]:
    # 提示而非强制建表:生产用 alembic upgrade head,本地 dev 也建议先跑一次 migration。
    # 这里只 log,不自动 create_all,避免生产环境意外建出过时 schema。
    logger.info(
        "started env=%s debug=%s db=%s",
        settings.APP_ENV,
        settings.APP_DEBUG,
        settings.DATABASE_URL.split("://", 1)[0],
    )
    get_pricebot_client()  # 预热透传 client:把建 SSL 上下文的一次性成本付在启动,首个领券请求即热
    if settings.mt_cps_configured:
        # 同理预热美团 CPS client(TLS 上下文 + 连接池建一次,后续 keep-alive 复用)
        mt_meituan.get_client()
    try:
        # 预热离线地理库:首次加载 ~2.5M 行 CSV + 建 KDTree,摊到启动、不砸首个按城市过滤的请求
        from app.utils import geo
        geo.ensure_loaded()
    except Exception:  # noqa: BLE001
        logger.exception("reverse_geocoder 预热失败(城市反查将在首个请求时懒加载)")
    reconcile_task = start_withdraw_reconcile_worker()
    cps_reconcile_task = start_cps_reconcile_worker()
    heartbeat_task = start_heartbeat_monitor()
    daily_exchange_task = start_daily_exchange_worker()
    observe_task = start_observe_worker()
    inactivity_task = start_inactivity_reset_worker()
    llm_cost_backfill_task = start_llm_cost_backfill_worker()
    try:
        yield
    finally:
        await stop_heartbeat_monitor(heartbeat_task)
        await stop_withdraw_reconcile_worker(reconcile_task)
        await stop_cps_reconcile_worker(cps_reconcile_task)
        await stop_daily_exchange_worker(daily_exchange_task)
        await stop_observe_worker(observe_task)
        await stop_inactivity_reset_worker(inactivity_task)
        await stop_llm_cost_backfill_worker(llm_cost_backfill_task)
        await aclose_pricebot_client()
        mt_meituan.close_client()
        logger.info("shutting down")


app = FastAPI(
    title=settings.APP_NAME,
    version="0.1.0",
    docs_url="/docs" if not settings.is_prod else None,
    redoc_url="/redoc" if not settings.is_prod else None,
    lifespan=lifespan,
)

if settings.cors_origins_list:
    app.add_middleware(
        CORSMiddleware,
        allow_origins=settings.cors_origins_list,
        allow_credentials=True,
        allow_methods=["*"],
        allow_headers=["*"],
    )

# 接口指标埋点(放在 CORS 之后 = 最外层:测到含 CORS 的完整耗时)。未配置观测时中间件自 no-op。
app.add_middleware(RequestMetricsMiddleware)


@app.get("/health", tags=["meta"])
def health() -> dict[str, str]:
    return {"status": "ok"}


app.include_router(auth_router)
app.include_router(user_router)
app.include_router(feedback_router)
app.include_router(analytics_router)
app.include_router(applog_router)
app.include_router(invite_router)
app.include_router(coupon_router)
app.include_router(device_router)
app.include_router(compare_router)
app.include_router(compare_record_router)
app.include_router(compare_milestone_router)
app.include_router(meituan_router)
app.include_router(wallet_router)
app.include_router(wxpay_router)
app.include_router(signin_router)
app.include_router(tasks_router)
app.include_router(savings_router)
app.include_router(ad_router)
# 新手引导视频(领券等候浮层前 N 次替代广告;运营后台传片,见 repositories/guide_video.py)
app.include_router(guide_video_router)
app.include_router(order_router)
app.include_router(report_router)
# 消息通知中心(PRD;数据落库 notification 表,见 repositories/notification.py)
app.include_router(notifications_router)
# 厂商推送测试三件套(配置状态/模板预览/测试发送,支持 mock 与真发)
app.include_router(push_router)
# 内部(server→server)端点:pricebot 上报价格观测 / 店铺映射,靠共享密钥头校验,不对客户端开放。
app.include_router(internal_price_router)
app.include_router(internal_store_router)
app.include_router(internal_app_version_router)
app.include_router(internal_launch_confirm_router)
app.include_router(platform_router)
# CPS 群发短链跳转 /c/{code}(公网无鉴权:记点击 → 302 跳美团)
app.include_router(cps_redirect_router)

# 用户上传文件(头像)静态服务。生产可改由 nginx 直接 serve MEDIA_ROOT。
_media_root = Path(settings.MEDIA_ROOT)
_media_root.mkdir(parents=True, exist_ok=True)


# 官网下载的 APK 直链(落地页 dl.html「官网下载」按钮指向 /media/shaguabijia.apk)。
# 必须在 StaticFiles 挂载【之前】注册,否则被静态挂载吃掉。
# StaticFiles 给 .apk 的 Content-Type 不对、且无 attachment 头 → 部分国产浏览器不触发下载、转甩应用市场;
# 这里显式回 application/vnd.android.package-archive + Content-Disposition:attachment 强制浏览器下载。
# 文件由 scripts/publish_apk.sh 编 release 包后放到 data/media/shaguabijia.apk(*.apk 不入 git,需部署时放)。
_APK_PATH = _media_root / "shaguabijia.apk"


@app.get(f"{settings.MEDIA_URL_PREFIX}/shaguabijia.apk", tags=["meta"], include_in_schema=False)
def download_apk() -> FileResponse:
    if not _APK_PATH.is_file():
        from fastapi import HTTPException

        raise HTTPException(status_code=404, detail="安装包未就绪")
    return FileResponse(
        _APK_PATH,
        media_type="application/vnd.android.package-archive",
        filename="shaguabijia.apk",
        headers={"Content-Disposition": 'attachment; filename="shaguabijia.apk"'},
    )


app.mount(
    settings.MEDIA_URL_PREFIX,
    StaticFiles(directory=str(_media_root)),
    name="media",
)
