#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称：main.py
模块描述：FastAPI 应用入口，负责启动 Web 服务和注册路由

作者：czj
创建日期：2026-07-09
最后修改：2026-07-11
"""

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

# Windows 平台设置 ProactorEventLoop 以支持子进程操作
if sys.platform == 'win32':
    asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())

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

from app.config import settings
from app.database import init_db
from app.routers import modules, cases, executions, recorder, stats, reports, cleanup, batch, dependencies, security, security_erp, security_erp_upload, security_erp_task, ai_locator, device_sim, system, element_locator, login_template, smart_locate, performance, performance_output, api_preset, deploy, functional_report, report_template, api_test, debug, scheduled_tasks, security_scheduled_tasks

# 配置日志
logging.basicConfig(
    level=logging.INFO if not settings.DEBUG else logging.DEBUG,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)


@asynccontextmanager
async def lifespan(app: FastAPI):
    """
    应用生命周期管理

    在应用启动时初始化数据库，在关闭时清理资源。

    Yields:
        None
    """
    # 启动时
    logger.info(f"正在启动 {settings.APP_NAME} v{settings.APP_VERSION}")

    # 确保必要的目录存在
    Path(settings.SCREENSHOT_DIR).mkdir(parents=True, exist_ok=True)
    Path(settings.REPORT_DIR).mkdir(parents=True, exist_ok=True)
    logger.info("目录初始化完成")

    await init_db()
    logger.info("数据库初始化完成")

    # 初始化默认登录模板
    try:
        from app.services.login_template_service import LoginTemplateService
        from app.database import async_session_maker
        async with async_session_maker() as session:
            service = LoginTemplateService(session)
            await service.ensure_default_templates()
            await session.commit()
        logger.info("默认登录模板初始化完成")
    except Exception as e:
        logger.warning(f"默认登录模板初始化失败（非致命）: {e}")

    # 加载被测系统 URL 与认证路径（从数据库持久化值，修复服务重启后回退到环境变量默认值的问题）
    try:
        from app.models.dingtalk_config import DingTalkConfig
        from sqlalchemy import select
        async with async_session_maker() as session:
            result = await session.execute(
                select(DingTalkConfig).where(DingTalkConfig.id == 1)
            )
            cfg = result.scalar_one_or_none()
            if cfg and cfg.target_url:
                settings.TARGET_URL = cfg.target_url
                logger.info(f"被测系统 URL 已从数据库加载: {settings.TARGET_URL}")
            if cfg and cfg.login_path:
                settings.AUTH_LOGIN_PATH = cfg.login_path
            if cfg and cfg.captcha_path:
                settings.AUTH_CAPTCHA_PATH = cfg.captcha_path
            if cfg and (cfg.login_path or cfg.captcha_path):
                logger.info(
                    f"认证接口路径已从数据库加载: login={settings.AUTH_LOGIN_PATH}, captcha={settings.AUTH_CAPTCHA_PATH}"
                )
    except Exception as e:
        logger.warning(f"加载被测系统 URL 失败（使用环境变量默认值）: {e}")

    # 启动消息消费循环
    consumer_task = None
    try:
        from app.services.device_sim_service import message_consumer_loop
        consumer_task = asyncio.create_task(message_consumer_loop())
        logger.info("消息消费循环已启动")
    except Exception as e:
        logger.warning(f"消息消费循环启动失败: {e}")

    # 启动定时任务调度引擎
    scheduler_task = None
    try:
        from app.services.scheduler_service import start_scheduler
        scheduler_task = start_scheduler()
        logger.info("定时任务调度引擎已启动")
    except Exception as e:
        logger.warning(f"定时任务调度引擎启动失败: {e}")

    yield

    # 关闭时
    logger.info("应用正在关闭...")

    # 停止消息消费循环
    if consumer_task:
        consumer_task.cancel()
        try:
            await consumer_task
        except asyncio.CancelledError:
            pass
        logger.info("消息消费循环已停止")

    # 停止定时任务调度引擎
    if scheduler_task:
        scheduler_task.cancel()
        try:
            await scheduler_task
        except asyncio.CancelledError:
            pass
        logger.info("定时任务调度引擎已停止")


# 创建 FastAPI 应用实例
app = FastAPI(
    title=settings.APP_NAME,
    description="Web 可视化自动化测试平台 API - 支持用例录制、执行、结果分析",
    version=settings.APP_VERSION,
    lifespan=lifespan,
    docs_url="/docs",
    redoc_url="/redoc",
)

# 配置 CORS 中间件
app.add_middleware(
    CORSMiddleware,
    allow_origins=settings.CORS_ORIGINS,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


# ==================== 注册路由 ====================

app.include_router(
    modules.router,
    prefix="/api/modules",
    tags=["模块管理"]
)

app.include_router(
    cases.router,
    prefix="/api/cases",
    tags=["用例管理"]
)

app.include_router(
    executions.router,
    prefix="/api/executions",
    tags=["执行管理"]
)

app.include_router(
    recorder.router,
    prefix="/api/recorder",
    tags=["用例录制"]
)

app.include_router(
    stats.router,
    prefix="/api/stats",
    tags=["统计分析"]
)

app.include_router(
    reports.router,
    prefix="/api/reports",
    tags=["报告管理"]
)

app.include_router(
    cleanup.router,
    prefix="/api/cleanup",
    tags=["数据清理"]
)

app.include_router(
    batch.router,
    prefix="/api/batch",
    tags=["批量操作"]
)

app.include_router(
    dependencies.router,
    prefix="/api",
    tags=["依赖管理"]
)

app.include_router(
    security.router,
    prefix="/api/security",
    tags=["安全测试"]
)

app.include_router(
    security_erp.router,
    prefix="/api/security",
    tags=["安全测试-ERP配置"]
)

app.include_router(
    security_erp_upload.router,
    prefix="/api/security",
    tags=["安全测试-ERP上传"]
)

# 安全测试-ERP任务创建
app.include_router(
    security_erp_task.router,
    prefix="/api/security",
    tags=["安全测试-ERP任务创建"]
)

app.include_router(
    ai_locator.router,
    prefix="/api/ai",
    tags=["AI定位"]
)

app.include_router(
    device_sim.router,
    prefix="/api/device-sim",
    tags=["设备模拟"]
)

app.include_router(
    system.router,
    prefix="/api/system",
    tags=["系统管理"]
)

app.include_router(
    element_locator.router,
    prefix="/api/element",
    tags=["元素定位"]
)

app.include_router(
    smart_locate.router,
    prefix="/api/element",
    tags=["智能定位"]
)

app.include_router(
    login_template.router,
    prefix="/api/login-templates",
    tags=["登录模板"]
)

app.include_router(
    performance.router,
    prefix="/api/performance",
    tags=["性能测试"]
)

# 性能测试任务输出（响应捕获值，供 REF 引用验证）
app.include_router(
    performance_output.router,
    prefix="/api/performance",
    tags=["性能测试-输出"]
)

# API 接口预设管理
app.include_router(
    api_preset.router,
    prefix="/api/performance",
    tags=["性能测试-预设"]
)

app.include_router(
    deploy.router,
    prefix="/api/deploy",
    tags=["自动化部署测试"]
)

app.include_router(
    functional_report.router,
    prefix="/api/functional-report",
    tags=["功能测试报告"]
)

# 报告模板配置路由（自身 prefix=/templates，注册在 /api/functional-report 下）
app.include_router(
    report_template.router,
    prefix="/api/functional-report",
    tags=["功能测试报告"]
)

# 接口测试路由
app.include_router(
    api_test.router,
    prefix="/api/api-test",
    tags=["接口测试"]
)

# 调试只读接口（语义解析验证，无写入）
app.include_router(
    debug.router,
    prefix="/api/debug",
    tags=["调试"]
)

# UI 自动化定时任务管理
app.include_router(
    scheduled_tasks.router,
    prefix="/api/scheduled-tasks",
    tags=["定时任务"]
)

# 安全测试定时任务管理
app.include_router(
    security_scheduled_tasks.router,
    prefix="/api/security/scheduled-tasks",
    tags=["安全测试-定时任务"]
)


# ==================== 根路径 ====================

@app.get("/", tags=["健康检查"])
async def root():
    """
    根路径 — 容器部署时返回前端页面，否则返回健康检查 JSON

    Returns:
        FileResponse (前端 dist 存在时) 或 dict (健康检查)
    """
    # 容器部署时，前端 dist 存在，直接返回 index.html
    FRONTEND_DIST = Path(__file__).parent.parent / "frontend" / "dist"
    if FRONTEND_DIST.exists():
        return FileResponse(str(FRONTEND_DIST / "index.html"))

    # 本地开发时，返回健康检查 JSON
    return {
        "status": "ok",
        "message": f"{settings.APP_NAME} API 服务运行中",
        "version": settings.APP_VERSION
    }


@app.get("/health", tags=["健康检查"])
async def health_check():
    """
    详细健康检查

    返回服务和数据库连接状态。

    Returns:
        dict: 详细健康状态
    """
    return {
        "status": "healthy",
        "service": settings.APP_NAME,
        "version": settings.APP_VERSION,
        "debug": settings.DEBUG
    }


# ==================== 文件服务 ====================

@app.get("/api/files/{file_path:path}", tags=["文件服务"])
async def serve_file(file_path: str):
    """
    提供截图等静态文件访问

    支持路径格式：自定义相对路径

    Args:
        file_path (str): 文件相对路径

    Returns:
        FileResponse: 文件响应
    """
    # 支持多种可能的路径
    possible_paths = [
        Path(file_path),                      # 直接路径
        Path("data") / file_path,             # data/ 前缀
        Path(settings.SCREENSHOT_DIR) / Path(file_path).name,  # 截图目录
    ]

    for p in possible_paths:
        resolved = p.resolve()
        if resolved.exists() and resolved.is_file():
            return FileResponse(str(resolved))

    raise HTTPException(
        status_code=404,
        detail=f"文件不存在: {file_path}"
    )


# ==================== 前端静态文件服务（容器化部署用） ====================

FRONTEND_DIST = Path(__file__).parent.parent / "frontend" / "dist"

if FRONTEND_DIST.exists():
    logger.info(f"挂载前端静态文件: {FRONTEND_DIST}")

    # 挂载静态资源目录（JS/CSS/图片等）
    app.mount("/assets", StaticFiles(directory=FRONTEND_DIST / "assets"), name="assets")

    # 前端 SPA 路由：所有非 API 路径回退到 index.html
    @app.get("/{full_path:path}")
    async def serve_spa(full_path: str):
        """
        前端 SPA 入口，所有非 API 路径返回 index.html

        注意：此路由必须注册在所有 API 路由之后，避免覆盖 API 路由。
        """
        file_path = FRONTEND_DIST / full_path
        if file_path.exists() and file_path.is_file():
            return FileResponse(str(file_path))
        return FileResponse(str(FRONTEND_DIST / "index.html"))