"""
NeoZQYY 后端 API 入口

基于 FastAPI 构建，为管理后台和微信小程序提供 RESTful API。
OpenAPI 文档自动生成于 /docs（Swagger UI）和 /redoc（ReDoc）。
"""

from contextlib import asynccontextmanager

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from starlette.exceptions import HTTPException as StarletteHTTPException

from app.middleware.response_wrapper import (
    ResponseWrapperMiddleware,
    http_exception_handler,
    unhandled_exception_handler,
)
from app.trace.middleware import TraceMiddleware

from app import config
# CHANGE 2026-02-19 | 新增 xcx_test 路由（MVP 验证）+ wx_callback 路由（微信消息推送）
# CHANGE 2026-02-23 | 新增 ops_panel 路由（运维控制面板）
# CHANGE 2026-02-25 | 新增 xcx_auth 路由（小程序微信登录 + 申请 + 状态查询 + 店铺切换）
# CHANGE 2026-02-26 | member_birthday 路由替换为 member_retention_clue（维客线索重构）
# CHANGE 2026-02-26 | 新增 admin_applications 路由（管理端申请审核）
# CHANGE 2026-02-27 | 新增 xcx_tasks / xcx_notes 路由（小程序核心业务）
# CHANGE 2026-03-09 | 新增 xcx_ai_chat 路由（AI SSE 对话 + 历史对话）→ 2026-03-20 迁移为 xcx_chat（/api/xcx/chat/*）
# CHANGE 2026-03-09 | 新增 xcx_ai_cache 路由（AI 缓存查询）
# CHANGE 2026-03-18 | 新增 xcx_customers 路由（CUST-1 客户详情、CUST-2 客户服务记录）
# CHANGE 2026-03-19 | 新增 xcx_coaches 路由（COACH-1 助教详情）
# CHANGE 2026-03-19 | 新增 xcx_board / xcx_config 路由（RNS1.3 三看板 + 技能类型配置）
# CHANGE 2026-03-22 | 新增 admin_registry 路由（NS4.1 注册体系：租户/店铺/简写ID 管理）
# CHANGE 2026-03-23 | 新增 admin_ai 路由（P15 AI 监控后台：Dashboard/调度/调用/缓存/预算/批量/告警）
# CHANGE 2026-03-24 | 新增 admin_dev_trace 路由（dev-trace-log: 开发调试日志管理 API）
# CHANGE 2026-03-23 | 新增 trigger_jobs 路由（定时任务管理页面 API）
# CHANGE 2026-03-24 | P18 任务引擎运营看板：新增 admin_task_engine 路由
# CHANGE 2026-03-29 | DWS_TASK_ENGINE：新增 internal_events 路由（按 job_name 执行任务）
from app.routers import auth, execution, schedules, tasks, env_config, db_viewer, etl_status, xcx_test, wx_callback, member_retention_clue, ops_panel, xcx_auth, xcx_avatar, admin_applications, business_day, xcx_tasks, xcx_notes, xcx_chat, xcx_ai_cache, xcx_performance, xcx_customers, xcx_coaches, xcx_board, xcx_config, xcx_runtime_clock, tenant_auth, tenant_users, tenant_excel, tenant_clues, tenant_site_admins, admin_tenant_admins, admin_registry, internal_ai, admin_ai, admin_dev_trace, trigger_jobs, admin_task_engine, admin_db_health, admin_triggers, internal_events, admin_runtime_context
from app.services.scheduler import scheduler
from app.services.task_queue import task_queue
from app.services.task_executor import task_executor
from app.ws.logs import ws_router
from app.ws.ai_events import ws_router as ai_ws_router


@asynccontextmanager
async def lifespan(app: FastAPI):
    """应用生命周期：启动时拉起后台服务，关闭时优雅停止。"""
    # CHANGE 2026-03-07 | 启动横幅：打印关键路径，便于诊断连到了哪个实例
    import sys
    _banner = (
        "\n"
        "╔══════════════════════════════════════════════════════╗\n"
        "║           NeoZQYY Backend — 启动诊断                ║\n"
        "╠══════════════════════════════════════════════════════╣\n"
        f"║  Python:    {sys.executable}\n"
        f"║  ROOT:      {config._project_root}\n"
        f"║  ETL_PATH:  {config.ETL_PROJECT_PATH}\n"
        f"║  ETL_PY:    {config.ETL_PYTHON_EXECUTABLE}\n"
        f"║  OPS_BASE:  {config.OPS_SERVER_BASE}\n"
        f"║  APP_DB:    {config.APP_DB_NAME}\n"
        f"║  .env:      {config._root_env}\n"
        "╚══════════════════════════════════════════════════════╝\n"
    )
    print(_banner, flush=True)

    # CHANGE 2026-03-22 | 启动时清理本机僵尸任务（上次非正常关闭遗留的 running 记录）
    task_executor.recover_stale()

    # 启动
    task_queue.start()
    scheduler.start()

    # CHANGE 2026-02-27 | 注册触发器 job handler（核心业务模块）
    # CHANGE 2026-03-24 | dev-trace-log: 用 trace_job 包装 job handler，追踪后台任务执行
    from app.services.trigger_scheduler import register_job
    from app.services import task_generator, task_expiry, recall_detector, note_reclassifier
    from app.trace.job_wrapper import trace_job

    register_job("task_generator", trace_job("task_generator")(lambda **_kw: task_generator.run()))
    register_job("task_expiry_check", trace_job("task_expiry_check")(lambda **_kw: task_expiry.run()))
    register_job("recall_completion_check", trace_job("recall_completion_check")(recall_detector.run))
    register_job("note_reclassify_backfill", trace_job("note_reclassify_backfill")(note_reclassifier.run))

    # CHANGE 2026-03-23 | 启动时检查定时任务是否今天执行过，打印提示
    from app.services.trigger_scheduler import check_startup_jobs
    try:
        pending_jobs = check_startup_jobs()
        if pending_jobs:
            _lines = ["╔══ 定时任务提醒 ══════════════════════════════════════╗"]
            for j in pending_jobs:
                _lines.append(f"║  ⚠ {j['description']}（{j['job_name']}）— {j['last_run_at']}")
            _lines.append("║  → 请在管理后台「定时任务」页面手动执行")
            _lines.append("╚══════════════════════════════════════════════════════╝")
            print("\n".join(_lines), flush=True)
        else:
            print("✓ 所有定时任务今天已执行过", flush=True)
    except Exception:
        import logging as _log
        _log.getLogger(__name__).warning("启动检查定时任务失败", exc_info=True)

    # CHANGE 2026-04-22 | 启动时清理上次进程遗留的孤儿 run_logs（worker 被 kill/reload 导致 status 卡在 running）
    try:
        from app.database import get_connection as _get_conn_cleanup
        _c = _get_conn_cleanup()
        try:
            with _c.cursor() as _cur:
                _cur.execute(
                    """
                    UPDATE biz.ai_run_logs
                    SET status = 'failed',
                        error_message = COALESCE(error_message, '') || ' [orphaned_by_restart]',
                        finished_at = COALESCE(finished_at, NOW())
                    WHERE status = 'running'
                      AND created_at < NOW() - INTERVAL '5 minutes'
                    """
                )
                _cleaned = _cur.rowcount
            _c.commit()
            if _cleaned:
                import logging as _log
                _log.getLogger(__name__).info("启动清理 %d 条孤儿 run_logs（status=running > 5min）", _cleaned)
        finally:
            _c.close()
    except Exception:
        import logging as _log
        _log.getLogger(__name__).warning("孤儿 run_logs 清理失败（忽略）", exc_info=True)

    # CHANGE 2026-03-10 | 注册 AI 事件处理器（消费/备注/任务分配 → AI 调用链）
    # CHANGE 2026-03-22 | P14 迁移：BailianClient → DashScopeClient + AIConfig + 防护层
    try:
        from app.ai.config import AIConfig
        from app.ai.dashscope_client import DashScopeClient
        from app.ai.cache_service import AICacheService
        from app.ai.conversation_service import ConversationService
        from app.ai.circuit_breaker import CircuitBreaker
        from app.ai.rate_limiter import RateLimiter
        from app.ai.budget_tracker import BudgetTracker
        from app.ai.run_log_service import AIRunLogService
        from app.ai.dispatcher import AIDispatcher, register_ai_handlers
        from app.database import get_connection

        _ai_config = AIConfig.from_env()
        _client = DashScopeClient(api_key=_ai_config.api_key, workspace_id=_ai_config.workspace_id)
        _run_log_svc = AIRunLogService(get_conn=get_connection)
        _dispatcher = AIDispatcher(
            client=_client,
            cache_svc=AICacheService(),
            conv_svc=ConversationService(),
            circuit_breaker=CircuitBreaker(),
            rate_limiter=RateLimiter(),
            budget_tracker=BudgetTracker(usage_provider=_run_log_svc),
            run_log_svc=_run_log_svc,
            config=_ai_config,
        )
        register_ai_handlers(_dispatcher)
        from app.routers import internal_ai as _internal_ai_router
        _internal_ai_router.set_dispatcher(_dispatcher)
    except Exception:
        import logging as _log
        _log.getLogger(__name__).warning("AI 事件处理器注册失败，AI 功能不可用", exc_info=True)

    yield
    # CHANGE 2026-03-22 | 优雅关闭：先终止所有运行中的子进程（3s 超时），再停调度和队列
    await task_executor.shutdown(timeout=3.0)
    await scheduler.stop()
    await task_queue.stop()


app = FastAPI(
    title="NeoZQYY API",
    description="台球门店运营助手 — 后端 API（管理后台 + 微信小程序）",
    version="0.1.0",
    docs_url="/docs",
    redoc_url="/redoc",
    lifespan=lifespan,
)

# ---- CORS 中间件 ----
# 允许来源从环境变量 CORS_ORIGINS 读取，缺省允许 Vite 开发服务器 (localhost:5173)
app.add_middleware(
    CORSMiddleware,
    allow_origins=config.CORS_ORIGINS,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# ---- 全局响应包装中间件（在 CORS 之后添加，执行顺序在 CORS 内层） ----
# CHANGE 2026-03-16 | RNS1.0 T0-1: 全局响应包装 + 异常处理器
app.add_middleware(ResponseWrapperMiddleware)

# ---- 全链路追踪中间件（最后添加 = 最先执行 = 最外层） ----
# CHANGE 2026-03-24 | dev-trace-log: TraceMiddleware 包裹所有中间件，仅拦截 /api/xcx/ 路由
app.add_middleware(TraceMiddleware)

# ---- 全局异常处理器 ----
app.add_exception_handler(StarletteHTTPException, http_exception_handler)
app.add_exception_handler(Exception, unhandled_exception_handler)

# ---- 路由注册 ----
app.include_router(auth.router)
app.include_router(tasks.router)
app.include_router(execution.router)
app.include_router(schedules.router)
app.include_router(env_config.router)
app.include_router(db_viewer.router)
app.include_router(etl_status.router)
app.include_router(ws_router)
app.include_router(ai_ws_router)
app.include_router(xcx_test.router)
app.include_router(wx_callback.router)
app.include_router(member_retention_clue.router)
app.include_router(ops_panel.router)
app.include_router(xcx_auth.router)
app.include_router(xcx_avatar.router)
app.include_router(admin_applications.router)
app.include_router(business_day.router)
app.include_router(xcx_tasks.router)
app.include_router(xcx_notes.router)
app.include_router(xcx_chat.router)
app.include_router(xcx_ai_cache.router)
app.include_router(xcx_performance.router)
app.include_router(xcx_customers.router)
app.include_router(xcx_coaches.router)
app.include_router(xcx_board.router)
app.include_router(xcx_config.router)
app.include_router(xcx_runtime_clock.router)
app.include_router(tenant_auth.router)
app.include_router(tenant_users.router)
app.include_router(tenant_excel.router)
app.include_router(tenant_clues.router)
app.include_router(tenant_site_admins.router)
app.include_router(admin_tenant_admins.router)
app.include_router(admin_registry.router)
app.include_router(internal_ai.router)
app.include_router(internal_events.router)
app.include_router(admin_ai.router)
app.include_router(admin_dev_trace.router)
app.include_router(trigger_jobs.router)
app.include_router(admin_task_engine.router)
app.include_router(admin_db_health.router)
app.include_router(admin_triggers.router)
app.include_router(admin_runtime_context.router)
app.include_router(admin_runtime_context.config_router)


@app.get("/health", tags=["系统"])
async def health_check():
    """健康检查端点，用于探活和监控。"""
    return {"status": "ok"}


# CHANGE 2026-03-07 | 诊断端点：返回关键路径配置，用于确认连到的是哪个实例
@app.get("/debug/config-paths", tags=["系统"])
async def debug_config_paths():
    """返回当前后端实例的关键路径配置（仅开发环境使用）。"""
    import sys
    import os
    import platform
    from app.services.cli_builder import cli_builder as _cb
    from app.schemas.tasks import TaskConfigSchema as _TCS

    _test_cfg = _TCS(flow="api_ods_dwd", processing_mode="increment_only",
                     tasks=["DWD_LOAD_FROM_ODS"], store_id=123)
    _test_cmd = _cb.build_command(
        _test_cfg, config.ETL_PROJECT_PATH,
        python_executable=config.ETL_PYTHON_EXECUTABLE,
    )
    _test_cmd_str = " ".join(_test_cmd)

    return {
        "hostname": platform.node(),
        "python_executable": sys.executable,
        "project_root": str(config._project_root),
        "env_file": str(config._root_env),
        "etl_python_executable": config.ETL_PYTHON_EXECUTABLE,
        "etl_project_path": config.ETL_PROJECT_PATH,
        "simulated_command": _test_cmd_str,
        "NEOZQYY_ROOT_env": os.environ.get("NEOZQYY_ROOT", "<未设置>"),
        "cwd": os.getcwd(),
    }
