#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称：executions.py
模块描述：执行管理 API 路由

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

import logging
from typing import Optional, List
from datetime import datetime
from pathlib import Path

from fastapi import APIRouter, Depends, HTTPException, Query, WebSocket, WebSocketDisconnect, BackgroundTasks
from sqlalchemy.ext.asyncio import AsyncSession

from app.database import get_db
from app.services.execution_service import ExecutionService, is_execution_running
from app.websocket.manager import manager
from app.config import settings

logger = logging.getLogger(__name__)

router = APIRouter()


def _run_execution_worker(exec_id: str, config_path: str, base_dir: str):
    """
    子进程工作函数（模块级定义，用于 multiprocessing）

    此函数在独立的子进程中运行，负责执行测试任务。

    Args:
        exec_id (str): 执行记录ID
        config_path (str): 配置文件路径（JSON 格式）
        base_dir (str): 项目根目录路径
    """
    import asyncio
    import sys
    import json
    import os

    sys.path.insert(0, base_dir)

    try:
        with open(config_path) as f:
            data = json.load(f)
    except Exception:
        data = {"config": None}

    async def _run():
        from app.database import async_session_maker
        from app.services.execution_service import ExecutionService
        async with async_session_maker() as session:
            svc = ExecutionService(session)
            await svc.run_execution(exec_id, config=data.get("config"))

    asyncio.run(_run())

    # 清理临时文件
    try:
        os.remove(config_path)
    except OSError:
        pass


def get_execution_service(db: AsyncSession = Depends(get_db)) -> ExecutionService:
    """
    获取执行服务实例（依赖注入）

    Args:
        db (AsyncSession): 数据库会话

    Returns:
        ExecutionService: 执行服务实例
    """
    return ExecutionService(db)


@router.get("", response_model=dict)
async def list_executions(
    skip: int = Query(0, ge=0, description="跳过记录数"),
    limit: int = Query(50, ge=1, le=200, description="返回记录数上限"),
    status: Optional[str] = Query(None, description="状态筛选"),
    case_type: Optional[str] = Query(None, description="用例类型筛选: ui/api/security/deploy/performance/functional"),
    trigger_type: Optional[str] = Query(None, description="触发类型筛选: manual/scheduled"),
    trigger_by: Optional[str] = Query(None, description="触发人/任务名筛选"),
    service: ExecutionService = Depends(get_execution_service),
):
    """
    获取执行记录列表

    Args:
        skip (int): 跳过记录数
        limit (int): 返回记录数上限
        status (Optional[str]): 状态筛选
        case_type (Optional[str]): 用例类型筛选
        trigger_type (Optional[str]): 触发类型筛选
        trigger_by (Optional[str]): 触发人/任务名筛选
        service (ExecutionService): 执行服务实例

    Returns:
        dict: 执行记录列表
    """
    try:
        executions, total = await service.list_executions(
            skip=skip, limit=limit, status=status, case_type=case_type,
            trigger_type=trigger_type, trigger_by=trigger_by,
        )
        return {
            "total": total,
            "items": [e.to_dict() for e in executions],
        }

    except Exception as e:
        logger.error(f"获取执行记录列表失败: {str(e)}")
        raise HTTPException(status_code=500, detail=f"服务器内部错误: {str(e)}")


@router.get("/running-status", response_model=dict)
async def get_running_status():
    """
    查询当前是否有 UI 执行任务正在运行

    用于前端判断是否可以发起执行请求。

    Returns:
        dict: {"is_running": bool, "execution_id": str|null}
    """
    is_running, execution_id = is_execution_running()
    return {
        "is_running": is_running,
        "execution_id": execution_id,
    }


@router.post("", response_model=dict, status_code=201)
async def create_execution(
    data: dict,
    service: ExecutionService = Depends(get_execution_service),
):
    """
    创建执行任务

    请求体格式:
        {
            "case_ids": ["case_001", "case_002"],
            "name": "可选执行名称",
            "environment": "test",
            "config": {"headless": true, "timeout": 30000}
        }

    Args:
        data (dict): 创建数据
        service (ExecutionService): 执行服务实例

    Returns:
        dict: 执行记录数据
    """
    try:
        case_ids = data.get("case_ids", [])
        if not case_ids:
            raise HTTPException(status_code=400, detail="case_ids 不能为空")

        execution = await service.create_execution(
            case_ids=case_ids,
            name=data.get("name", ""),
            trigger_type=data.get("trigger_type", "manual"),
            trigger_by=data.get("trigger_by", "user"),
            environment=data.get("environment", "default"),
            config=data.get("config"),
        )

        return execution.to_dict()

    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"创建执行任务失败: {str(e)}")
        raise HTTPException(status_code=500, detail=f"服务器内部错误: {str(e)}")


@router.get("/{execution_id}", response_model=dict)
async def get_execution(
    execution_id: str,
    service: ExecutionService = Depends(get_execution_service),
):
    """
    获取执行详情

    Args:
        execution_id (str): 执行记录ID
        service (ExecutionService): 执行服务实例

    Returns:
        dict: 执行详情
    """
    try:
        execution = await service.get_execution(execution_id)
        if not execution:
            raise HTTPException(status_code=404, detail=f"执行记录不存在: {execution_id}")
        return execution.to_dict()

    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"获取执行详情失败: {str(e)}")
        raise HTTPException(status_code=500, detail=f"服务器内部错误: {str(e)}")


@router.post("/{execution_id}/run", response_model=dict)
async def run_execution(
    execution_id: str,
    config: Optional[dict] = None,
    service: ExecutionService = Depends(get_execution_service),
    background_tasks: BackgroundTasks = None,
):
    """
    执行测试任务

    开始执行已创建的执行记录。

    Args:
        execution_id (str): 执行记录ID
        config (Optional[dict]): 执行配置覆盖
        service (ExecutionService): 执行服务实例
        background_tasks (BackgroundTasks): FastAPI 后台任务

    Returns:
        dict: 执行结果
    """
    from app.database import async_session_maker

    try:
        execution = await service.get_execution(execution_id)
        if not execution:
            raise HTTPException(status_code=404, detail=f"执行记录不存在: {execution_id}")

        if execution.status not in ("pending",):
            raise HTTPException(status_code=400, detail=f"执行任务状态为 {execution.status}，无法执行")

        # 先更新状态为 running，并提交到数据库
        execution.status = "running"
        execution.start_time = datetime.now()
        await service.db.flush()
        await service.db.commit()

        # 使用子进程在后台执行（避免 Windows 事件循环问题）
        # 同步执行（阻塞请求，适合 UI 测试场景）
        # 注意：执行时间取决于用例步骤数和目标网站响应
        # 前端需要设置足够的超时时间（建议 5 分钟）
        result = await service.run_execution(execution_id, config=config)

        return {"message": "执行完成", "execution_id": execution_id, "status": result.status}

    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"执行任务失败: {str(e)}")
        raise HTTPException(status_code=500, detail=f"服务器内部错误: {str(e)}")


@router.post("/{execution_id}/cancel", response_model=dict)
async def cancel_execution(
    execution_id: str,
    service: ExecutionService = Depends(get_execution_service),
):
    """
    取消执行任务

    Args:
        execution_id (str): 执行记录ID
        service (ExecutionService): 执行服务实例

    Returns:
        dict: 取消结果
    """
    try:
        cancelled = await service.cancel_execution(execution_id)
        if not cancelled:
            raise HTTPException(status_code=404, detail=f"执行记录不存在: {execution_id}")

        return {"message": "执行任务已取消", "execution_id": execution_id}

    except HTTPException:
        raise
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        logger.error(f"取消执行任务失败: {str(e)}")
        raise HTTPException(status_code=500, detail=f"服务器内部错误: {str(e)}")


@router.get("/{execution_id}/results", response_model=dict)
async def get_execution_results(
    execution_id: str,
    service: ExecutionService = Depends(get_execution_service),
):
    """
    获取执行结果列表

    Args:
        execution_id (str): 执行记录ID
        service (ExecutionService): 执行服务实例

    Returns:
        dict: 结果列表
    """
    try:
        execution = await service.get_execution(execution_id)
        if not execution:
            raise HTTPException(status_code=404, detail=f"执行记录不存在: {execution_id}")

        items, total = await service.get_results(execution_id)
        return {
            "total": total,
            "items": [r.to_dict() for r in items],
        }

    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"获取执行结果失败: {str(e)}")
        raise HTTPException(status_code=500, detail=f"服务器内部错误: {str(e)}")


@router.get("/{execution_id}/results/{case_id}", response_model=dict)
async def get_case_result(
    execution_id: str,
    case_id: str,
    service: ExecutionService = Depends(get_execution_service),
):
    """
    获取单个用例的执行结果

    Args:
        execution_id (str): 执行记录ID
        case_id (str): 用例ID
        service (ExecutionService): 执行服务实例

    Returns:
        dict: 用例结果详情
    """
    try:
        result = await service.get_case_result(execution_id, case_id)
        if not result:
            raise HTTPException(status_code=404, detail=f"用例结果不存在")

        return result.to_dict()

    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"获取用例结果失败: {str(e)}")
        raise HTTPException(status_code=500, detail=f"服务器内部错误: {str(e)}")


# ==================== 删除接口 ====================

@router.delete("/{execution_id}", status_code=204)
async def delete_execution(
    execution_id: str,
    service: ExecutionService = Depends(get_execution_service),
):
    """
    删除执行记录

    删除执行记录及其关联的用例结果，**不影响测试用例本身**。
    执行记录与测试用例是独立的，删除执行记录只丢失本次执行的结果快照，
    用例定义保持不变，可再次执行生成新的执行记录。

    Args:
        execution_id (str): 执行记录ID
        service (ExecutionService): 执行服务实例

    Raises:
        HTTPException: 不存在返回404，运行中返回400
    """
    try:
        deleted = await service.delete_execution(execution_id)
        if not deleted:
            raise HTTPException(status_code=404, detail=f"执行记录不存在: {execution_id}")
    except ValueError as e:
        msg = str(e)
        if "运行中" in msg:
            raise HTTPException(status_code=400, detail=msg)
        raise HTTPException(status_code=404, detail=msg)
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"删除执行记录失败: {str(e)}")
        raise HTTPException(status_code=500, detail=f"服务器内部错误: {str(e)}")


@router.post("/batch-delete", response_model=dict)
async def batch_delete_executions(
    data: dict,
    service: ExecutionService = Depends(get_execution_service),
):
    """
    批量删除执行记录

    删除选中的执行记录及其关联的用例结果，**不影响测试用例本身**。
    运行中的执行记录会被自动跳过。

    Args:
        data (dict): {"ids": ["exec_id1", "exec_id2"]}
        service (ExecutionService): 执行服务实例

    Returns:
        dict: 删除结果统计（成功/失败/跳过数量 + 错误信息）
    """
    ids = data.get("ids", []) if isinstance(data, dict) else []
    if not ids:
        raise HTTPException(status_code=400, detail="未提供要删除的执行记录ID")
    try:
        success, failed, skipped, errors = await service.delete_executions(ids)
        return {
            "success_count": success,
            "failed_count": failed,
            "skipped_count": skipped,
            "errors": errors,
        }
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"批量删除执行记录失败: {str(e)}")
        raise HTTPException(status_code=500, detail=f"服务器内部错误: {str(e)}")


# ==================== WebSocket 端点 ====================

@router.websocket("/ws/{execution_id}")
async def websocket_execution(
    websocket: WebSocket,
    execution_id: str,
):
    """
    执行状态实时推送 WebSocket 端点

    客户端连接此端点后会收到：
    - execution_start: 执行开始
    - case_start: 用例开始
    - step_update: 步骤更新
    - case_complete: 用例完成
    - execution_complete: 执行完成
    - execution_cancelled: 执行取消

    Args:
        websocket (WebSocket): WebSocket 连接
        execution_id (str): 执行记录ID
    """
    await manager.connect(websocket, execution_id)

    try:
        # 保持连接，接收客户端消息（心跳等）
        while True:
            await websocket.receive_text()
    except WebSocketDisconnect:
        await manager.disconnect(websocket, execution_id)
    except Exception as e:
        logger.error(f"WebSocket 连接异常: {str(e)}")
        await manager.disconnect(websocket, execution_id)