"""
FastAPI 入口檔。

`uvicorn app.main:app` 會載入這個檔的 `app` 變數作為 ASGI 應用程式。

職責:
  1. 建立 FastAPI 實例
  2. 設定 CORS(允許前端跨域呼叫)
  3. 掛載各路由模組(auth, users, ...)
  4. 提供 /health 健康檢查 endpoint
"""

from contextlib import asynccontextmanager
from pathlib import Path

from fastapi import FastAPI, HTTPException, Query
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, HTMLResponse
from fastapi.staticfiles import StaticFiles

from app.api import agent as agent_routes
from app.api import auth as auth_routes
from app.api import channels as channels_routes
from app.api import coba as coba_routes
from app.api import line_webhook as line_webhook_routes
from app.api import meetings as meetings_routes
from app.api import messages as messages_routes
from app.api import projects as projects_routes
from app.api import search as search_routes
from app.api import tasks as tasks_routes
from app.api import users as users_routes
from app.api import ws as ws_routes
from app.api import rdash as rdash_routes  # rdash 個人儀表板(獨立)
from app.config import settings


@asynccontextmanager
async def lifespan(app: FastAPI):
    """應用程式生命週期掛勾。"""
    # ---- startup ----
    import asyncio

    # 暖機 1:預先載入 embedding 模型(避免第一次 @庫柏 要等 5 分鐘下載+載入)
    # 用 to_thread 因為 fastembed 是同步 ONNX,直接 await 會阻塞 event loop
    async def _warmup():
        try:
            from app.services.coba_memory import _get_embedding_model
            await asyncio.to_thread(_get_embedding_model)
            # backfill 既有訊息進 Qdrant(讓 RAG 立即可用)
            from app.services.coba_memory import backfill_existing_messages
            await backfill_existing_messages()
        except Exception as e:
            import logging
            logging.getLogger("coba.warmup").exception("warmup 失敗:%s", e)

    # 用 task 不擋啟動;但保留 reference 避免 GC
    app.state._warmup_task = asyncio.create_task(_warmup())

    # 階段 8:scheduler — hourly ping + 09:00 brief + 18:00 wrap
    try:
        from app.services.scheduler import scheduler_loop
        app.state._scheduler_task = asyncio.create_task(scheduler_loop())
    except Exception:
        import logging
        logging.getLogger("coba.main").exception("scheduler 啟動失敗")
        app.state._scheduler_task = None

    # 階段 10-F:Tailscale Funnel watchdog(每 5 分鐘 self-check,通道斷自動重啟)
    try:
        from app.services.funnel_watchdog import funnel_watchdog_loop
        app.state._funnel_watchdog_task = asyncio.create_task(funnel_watchdog_loop())
    except Exception:
        import logging
        logging.getLogger("coba.main").exception("funnel_watchdog 啟動失敗")
        app.state._funnel_watchdog_task = None

    # 階段 10-B:Cooper 全域 SDK Client 暖機
    # 啟動時先 connect 一次,LINE 訊息進來不用每次 spawn(75s → 5-10s)
    try:
        from app.services.cooper_client import cooper_client_manager
        # 用 task 跑,不擋 lifespan 啟動。對 caller 而言 manager.respond() 會等到 startup 完成。
        async def _cooper_warmup():
            try:
                await cooper_client_manager.startup()
            except Exception:
                import logging
                logging.getLogger("coba.main").exception("Cooper SDK client 暖機失敗(LINE 對話會 fallback 到 query()-spawn 模式)")
        app.state._cooper_warmup_task = asyncio.create_task(_cooper_warmup())
    except Exception:
        import logging
        logging.getLogger("coba.main").exception("Cooper SDK client startup task 建立失敗")

    yield
    # ---- shutdown ----
    if getattr(app.state, "_scheduler_task", None):
        app.state._scheduler_task.cancel()
    if getattr(app.state, "_funnel_watchdog_task", None):
        app.state._funnel_watchdog_task.cancel()
    # 階段 10-B:關閉 Cooper SDK client(disconnect child process)
    try:
        from app.services.cooper_client import cooper_client_manager
        await cooper_client_manager.shutdown()
    except Exception:
        import logging
        logging.getLogger("coba.main").exception("Cooper SDK client shutdown 失敗")


app = FastAPI(
    title="山水話菁英團隊 · 行程規畫系統 · 後端 API",
    description=(
        "山水話菁英團隊的行程規畫系統。"
        "提供任務、對話、會議、知識庫等功能,由 AI 同事「庫柏(Cooper)」串連各模組。"
    ),
    version="0.1.0-stage1",
    lifespan=lifespan,
    # 讓 Swagger UI 顯示 Bearer token 輸入欄
    swagger_ui_parameters={"persistAuthorization": True},
)


# ---------------------------------------------------------------------------
# CORS:允許前端從不同網域呼叫 API
# ---------------------------------------------------------------------------
# 開發階段允許所有來源,上線前要改成白名單
_cors_origins = ["*"] if settings.ENVIRONMENT == "development" else [
    # TODO: 上線前在這裡列出實際的前端網域
    # "https://coba.example.com",
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=_cors_origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


# ---------------------------------------------------------------------------
# 路由掛載
# ---------------------------------------------------------------------------
app.include_router(auth_routes.router)
app.include_router(users_routes.router)
app.include_router(projects_routes.router)
app.include_router(tasks_routes.router)
app.include_router(channels_routes.router)
app.include_router(messages_routes.router)
app.include_router(meetings_routes.router)
app.include_router(search_routes.router)
app.include_router(coba_routes.router)
app.include_router(line_webhook_routes.router)
app.include_router(agent_routes.router)
app.include_router(ws_routes.router)
app.include_router(rdash_routes.router)  # rdash 個人儀表板(獨立)


# ---------------------------------------------------------------------------
# 健康檢查
# ---------------------------------------------------------------------------
@app.get("/health", tags=["system"])
async def health_check() -> dict[str, str]:
    """供 Docker / NAS / 監控系統呼叫,確認 API 還活著。"""
    return {"status": "ok", "service": "coba-api", "environment": settings.ENVIRONMENT}


# ---------------------------------------------------------------------------
# 階段 8 F:Magic Link 自動登入(從 LINE 點按鈕來的)
# ---------------------------------------------------------------------------
@app.get("/auth-from-line", include_in_schema=False)
async def auth_from_line(t: str = Query(..., description="一次性 magic token")):
    """驗 magic token → 發 access/refresh JWT → 回 HTML 把 token 寫入 localStorage 後跳轉到 /。"""
    from app.services.magic_link import consume_magic_token
    from app.core.security import create_access_token, create_refresh_token
    from app.database import AsyncSessionLocal
    from app.models.user import User
    from sqlalchemy import select

    try:
        user_id = consume_magic_token(t)
    except ValueError as e:
        return HTMLResponse(
            f"""<!DOCTYPE html><html lang="zh-TW"><head><meta charset="utf-8"><title>連結失效</title>
            <style>body{{font-family:sans-serif;background:#0a0a14;color:#f1f5f9;display:grid;place-items:center;min-height:100vh;margin:0;padding:20px;text-align:center}}
            h1{{color:#C8A86A;font-size:24px}} p{{color:#94a3b8}} a{{color:#7C6BF0}}</style></head>
            <body><div><h1>連結失效</h1><p>{e}</p><p><a href="/">回首頁</a></p></div></body></html>""",
            status_code=403,
        )

    async with AsyncSessionLocal() as db:
        user = (await db.execute(
            select(User).where(User.id == user_id, User.is_active.is_(True), User.deleted_at.is_(None))
        )).scalar_one_or_none()
        if user is None:
            raise HTTPException(status_code=404, detail="使用者不存在或已停用")

    access = create_access_token(user.id)
    refresh = create_refresh_token(user.id)

    # 回一個小 HTML 把 JWT 存進 localStorage 然後跳轉
    return HTMLResponse(f"""<!DOCTYPE html>
<html lang="zh-TW"><head>
<meta charset="utf-8"><title>登入中...</title>
<style>body{{font-family:'Noto Sans TC',sans-serif;background:#0a0a14;color:#f1f5f9;
display:grid;place-items:center;min-height:100vh;margin:0;text-align:center}}
.spinner{{width:40px;height:40px;border:3px solid #1e293b;border-top-color:#C8A86A;
border-radius:50%;animation:s 0.8s linear infinite;margin:20px auto}}
@keyframes s{{to{{transform:rotate(360deg)}}}}
h1{{color:#C8A86A;font-size:20px;letter-spacing:0.18em}}</style></head>
<body><div><h1>進入工作台中...</h1><div class="spinner"></div><p style="color:#94a3b8">{user.display_name},稍等一下</p></div>
<script>
  localStorage.setItem('coba_access_token', '{access}');
  localStorage.setItem('coba_refresh_token', '{refresh}');
  setTimeout(() => location.replace('/'), 600);
</script></body></html>""")


# ---------------------------------------------------------------------------
# 靜態網頁(階段 1 驗收用的登入頁)
# ---------------------------------------------------------------------------
# /          → static/index.html(普通人友善的登入 + 註冊頁)
# /docs      → Swagger UI(工程師 debug 用)
# /health    → 健康檢查 JSON
# /api/...   → 各種 API 端點
# ---------------------------------------------------------------------------
_STATIC_DIR = Path(__file__).resolve().parent.parent / "static"

if _STATIC_DIR.exists():
    # /static/* → 提供其他靜態檔案(CSS、圖片等),目前用不到但先掛著
    app.mount("/static", StaticFiles(directory=_STATIC_DIR), name="static")

    @app.get("/", include_in_schema=False)
    async def serve_index():
        """首頁回傳登入頁面。"""
        return FileResponse(_STATIC_DIR / "index.html")

    # 階段 5a:Service Worker 必須從根路徑提供,scope 才能涵蓋整站
    @app.get("/sw.js", include_in_schema=False)
    async def serve_sw():
        return FileResponse(
            _STATIC_DIR / "sw.js",
            media_type="application/javascript",
            headers={"Service-Worker-Allowed": "/", "Cache-Control": "no-cache"},
        )
else:
    # 沒有 static 資料夾就退回 JSON 首頁
    @app.get("/", tags=["system"])
    async def root() -> dict[str, str]:
        return {"service": "Cooper 後端 API", "docs": "/docs", "health": "/health"}
