#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称：execution_service.py
模块描述：执行服务，负责测试任务的调度、执行和结果收集

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

import asyncio
import logging
import subprocess
import sys
import os
import json
import threading
from pathlib import Path
from typing import Optional, List, Tuple, Dict, Any, Callable
from datetime import datetime
from concurrent.futures import ThreadPoolExecutor

from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, desc, or_

from app.models.execution import Execution
from app.models.case_result import CaseResult
from app.models.test_case import TestCase
from app.models.module import Module
from app.executors.playwright_executor import (
    PlaywrightExecutor,
    StepResult,
    CaseExecutionResult,
)
from app.websocket.manager import manager
from app.utils.id_generator import generate_id
from app.config import settings

logger = logging.getLogger(__name__)


class ExecutionService:
    """
    执行服务类

    负责测试执行的调度、执行和结果收集管理。
    支持单个用例执行、批量执行、模块执行和全量执行。

    Attributes:
        db (AsyncSession): 数据库会话对象

    Example:
        >>> service = ExecutionService(db)
        >>> execution = await service.create_execution(case_ids=["case_001"])
        >>> await service.run_execution(execution.id)
    """

    def __init__(self, db: AsyncSession):
        """
        初始化执行服务

        Args:
            db (AsyncSession): 数据库会话对象
        """
        self.db = db

    async def list_executions(
        self,
        skip: int = 0,
        limit: int = 50,
        status: Optional[str] = None,
        case_type: Optional[str] = None,
    ) -> Tuple[List[Execution], int]:
        """
        获取执行记录列表

        Args:
            skip (int): 跳过记录数
            limit (int): 返回记录数上限
            status (Optional[str]): 状态筛选
            case_type (Optional[str]): 用例类型筛选 (ui/api/security/deploy/performance/functional)

        Returns:
            Tuple[List[Execution], int]: (执行记录列表, 总数)
        """
        conditions = []
        if status:
            conditions.append(Execution.status == status)
        if case_type:
            conditions.append(Execution.case_type == case_type)

        count_query = select(func.count(Execution.id))
        if conditions:
            count_query = count_query.where(*conditions)
        total_result = await self.db.execute(count_query)
        total = total_result.scalar() or 0

        query = (
            select(Execution)
            .order_by(desc(Execution.created_at))
            .offset(skip)
            .limit(limit)
        )
        if conditions:
            query = query.where(*conditions)

        result = await self.db.execute(query)
        return list(result.scalars().all()), total

    async def get_execution(self, execution_id: str) -> Optional[Execution]:
        """
        获取执行记录详情

        Args:
            execution_id (str): 执行记录ID

        Returns:
            Optional[Execution]: 执行记录对象
        """
        query = select(Execution).where(Execution.id == execution_id)
        result = await self.db.execute(query)
        return result.scalar_one_or_none()

    async def create_execution(
        self,
        case_ids: List[str],
        name: str = "",
        trigger_type: str = "manual",
        trigger_by: str = "user",
        environment: str = "default",
        config: Optional[dict] = None,
    ) -> Execution:
        """
        创建执行记录

        Args:
            case_ids (List[str]): 要执行的用例ID列表
            name (str): 执行名称
            trigger_type (str): 触发类型
            trigger_by (str): 触发人
            environment (str): 执行环境
            config (Optional[dict]): 执行配置

        Returns:
            Execution: 执行记录对象
        """
        # 生成执行名称
        if not name:
            date_str = datetime.now().strftime("%Y%m%d-%H%M%S")
            name = f"测试执行-{date_str}"

        # 先查询用例，取首个用例的 case_type 作为执行记录的类型（用于按类型筛选）
        # 按模块分组 + order 排序，确保同模块内首用例（含登录）先执行
        cases_query = select(TestCase).where(TestCase.id.in_(case_ids)).order_by(
            TestCase.module_id, TestCase.order, TestCase.created_at
        )
        cases_result = await self.db.execute(cases_query)
        cases = list(cases_result.scalars().all())
        first_case_type = cases[0].case_type if cases else "ui"

        execution = Execution(
            id=generate_id("exec"),
            name=name,
            trigger_type=trigger_type,
            trigger_by=trigger_by,
            total_cases=len(case_ids),
            status="pending",
            environment=environment,
            config=config or {},
            case_type=first_case_type,
        )

        self.db.add(execution)
        await self.db.flush()
        await self.db.refresh(execution)

        # 创建用例结果记录
        for case in cases:
            case_result = CaseResult(
                id=generate_id("result"),
                execution_id=execution.id,
                case_id=case.id,
                case_name=case.name,
                status="pending",
            )
            self.db.add(case_result)

        await self.db.flush()
        logger.info(f"执行记录创建成功: id={execution.id}, name={name}, cases={len(case_ids)}, case_type={first_case_type}")
        return execution

    async def run_execution(
        self,
        execution_id: str,
        config: Optional[dict] = None,
    ) -> Execution:
        """
        执行测试任务

        异步执行所有关联的测试用例，实时推送执行状态。

        Args:
            execution_id (str): 执行记录ID
            config (Optional[dict]): 执行配置

        Returns:
            Execution: 更新后的执行记录
        """
        execution = await self.get_execution(execution_id)
        if not execution:
            raise ValueError(f"执行记录不存在: {execution_id}")

        # ===== 按用例类型分流：安全测试走专用执行器 =====
        if execution.case_type == "security":
            return await self._run_security_execution(execution_id, config)

        # 更新状态为运行中
        execution.status = "running"
        execution.start_time = datetime.now()
        await self.db.flush()

        # 广播执行开始
        await manager.broadcast(execution_id, {
            "type": "execution_start",
            "data": execution.to_dict(),
        })

        # 获取所有待执行的用例结果
        # JOIN TestCase 按模块分组 + order 排序，确保同模块内首用例（含登录）先执行
        results_query = (
            select(CaseResult)
            .join(TestCase, CaseResult.case_id == TestCase.id)
            .where(
                CaseResult.execution_id == execution_id,
                CaseResult.status == "pending",
            )
            .order_by(TestCase.module_id, TestCase.order, TestCase.created_at)
        )
        results_data = await self.db.execute(results_query)
        case_results = list(results_data.scalars().all())

        # 获取用例定义
        case_ids = [r.case_id for r in case_results]
        cases_query = select(TestCase).where(TestCase.id.in_(case_ids))
        cases_data = await self.db.execute(cases_query)
        cases_map = {c.id: c for c in cases_data.scalars().all()}

        # 在线程池中为每个用例使用独立执行器
        loop = asyncio.get_event_loop()

        # 定义一个共享的执行函数，复用同一个执行器
        def run_all_cases_sync(cases_list, exec_config, ev_loop, broadcast_fn):
            """
            在一个执行器中执行所有用例，保持登录态

            Args:
                cases_list: 用例字典列表
                exec_config: 执行配置
                ev_loop: 主事件循环（用于线程安全广播）
                broadcast_fn: 异步广播函数（async broadcast）
            """
            logger.info(f"[run_all_cases_sync] 开始执行, 用例数: {len(cases_list)}")

            executor_config = exec_config or {}
            # auto_login 由用例自身配置决定，不强制覆盖
            # 透传单步重试次数（默认 2）
            executor_config.setdefault("step_retry_count", exec_config.get("step_retry_count", 2) if exec_config else 2)

            executor = PlaywrightExecutor(config=executor_config)
            results = []

            try:
                executor.start()
                logger.info(f"[run_all_cases_sync] 执行器启动成功")

                # 第一个用例会自动登录
                for i, case_dict in enumerate(cases_list):
                    case_id = case_dict.get("id", "")
                    case_name = case_dict.get("name", "")
                    steps_count = len(case_dict.get("steps", []))
                    logger.info(f"⏳ 执行用例 {i+1}/{len(cases_list)}: {case_name} (steps={steps_count})")

                    # 构造线程安全的步骤回调（跨线程提交到事件循环）
                    def step_callback(step_result, _cid=case_id, _cname=case_name):
                        try:
                            asyncio.run_coroutine_threadsafe(
                                broadcast_fn(execution_id, {
                                    "type": "step_update",
                                    "data": {
                                        "execution_id": execution_id,
                                        "case_id": _cid,
                                        "case_name": _cname,
                                        "step_order": step_result.order,
                                        "step_name": step_result.name,
                                        "action": step_result.action,
                                        "status": step_result.status,
                                        "duration": step_result.duration,
                                        "screenshot": step_result.screenshot,
                                        "log": step_result.log,
                                        "error": step_result.error,
                                    },
                                }),
                                ev_loop,
                            )
                        except Exception as e:
                            logger.error(f"✗ 步骤回调提交失败: {str(e)}")

                    result = executor.execute_case(case=case_dict, callback=step_callback)
                    logger.info(f"[run_all_cases_sync] 用例 {case_name} 执行完成, status={result.status}")
                    results.append(result)

                logger.info(f"[run_all_cases_sync] 所有用例执行完成, 结果数: {len(results)}")
                return results
            except Exception as e:
                logger.error(f"[run_all_cases_sync] 异常: {type(e).__name__}: {e}")
                raise
            finally:
                logger.info("[run_all_cases_sync] 停止执行器")
                executor.stop()

        try:
            # 准备用例数据列表
            cases_to_run = []
            for case_result in case_results:
                case = cases_map.get(case_result.case_id)
                if case:
                    cases_to_run.append({
                        "case_result": case_result,
                        "case_dict": case.to_dict(),
                    })

            # 用专用线程执行 Playwright，避免 asyncio 线程池复用线程导致 greenlet 死锁。
            # 根因：sync_playwright().start() 在 ThreadPoolExecutor 复用的 worker 线程上
            # 会永久卡死（容器内已多次复现：仅打印「开始执行」，永不打印「执行器启动成功」）。
            # 专用线程不关联任何 asyncio 事件循环，sync API 可正常启动；
            # 步骤回调已通过 asyncio.run_coroutine_threadsafe(..., ev_loop) 跨线程回桥。
            exec_holder = {}  # {"data": [...]} 或 {"err": Exception}

            def _playwright_worker():
                try:
                    exec_holder["data"] = run_all_cases_sync(
                        [c["case_dict"] for c in cases_to_run],
                        config,
                        loop,
                        manager.broadcast,
                    )
                except Exception as e:  # noqa: BLE001 - 线程内异常需透传到主协程
                    exec_holder["err"] = e

            worker_thread = threading.Thread(
                target=_playwright_worker,
                name=f"playwright-exec-{execution_id}",
                daemon=True,
            )
            worker_thread.start()
            # 主协程挂起等待专用线程结束（run_in_executor 仅用于 join，不执行 Playwright）
            await loop.run_in_executor(None, worker_thread.join)

            if "err" in exec_holder:
                raise exec_holder["err"]
            all_results = exec_holder.get("data", [])

            # 更新结果
            for i, case_info in enumerate(cases_to_run):
                case_result = case_info["case_result"]
                result = all_results[i]

                # 更新状态为运行中（广播用例开始）
                case_result.status = "running"
                case_result.start_time = datetime.now()
                await self.db.flush()

                await manager.broadcast(execution_id, {
                    "type": "case_start",
                    "data": {
                        "execution_id": execution_id,
                        "case_id": case_result.case_id,
                        "case_name": case_result.case_name,
                    },
                })

                # 更新结果
                case_result.status = result.status
                case_result.duration = result.duration
                case_result.end_time = datetime.now()
                case_result.retry_count = result.retry_count
                case_result.error_message = result.error_message
                case_result.steps_result = [
                    {"order": s.order, "name": s.name, "action": s.action,
                     "status": s.status, "duration": s.duration,
                     "screenshot": s.screenshot, "log": s.log, "error": s.error}
                    for s in result.steps_result
                ]

                # 更新执行统计
                execution.passed = await self._count_results(execution_id, "passed")
                execution.failed = await self._count_results(execution_id, "failed")
                execution.skipped = await self._count_results(execution_id, "skipped")
                execution.pass_rate = execution.calculate_pass_rate()
                await self.db.flush()

                # 广播用例完成
                await manager.broadcast(execution_id, {
                    "type": "case_complete",
                    "data": case_result.to_dict(),
                })

        except Exception as e:
            logger.error(f"执行过程异常: {str(e)}")
            remaining = await self._get_pending_results(execution_id)
            for r in remaining:
                r.status = "failed"
                r.error_message = f"执行过程异常: {str(e)}"
            await self.db.flush()

        finally:
            # 更新执行状态
            execution.status = "completed"
            execution.end_time = datetime.now()
            execution.duration = (execution.end_time - execution.start_time).total_seconds()
            execution.passed = await self._count_results(execution_id, "passed")
            execution.failed = await self._count_results(execution_id, "failed")
            execution.skipped = await self._count_results(execution_id, "skipped")
            execution.pass_rate = execution.calculate_pass_rate()
            await self.db.flush()

            # 广播执行完成
            await manager.broadcast(execution_id, {
                "type": "execution_complete",
                "data": execution.to_dict(),
            })

            logger.info(
                f"执行完成: id={execution_id}, "
                f"通过={execution.passed}, 失败={execution.failed}, "
                f"耗时={execution.duration:.2f}s"
            )

        return execution

    async def cancel_execution(self, execution_id: str) -> bool:
        """
        取消执行任务

        Args:
            execution_id (str): 执行记录ID

        Returns:
            bool: 取消是否成功
        """
        execution = await self.get_execution(execution_id)
        if not execution:
            return False

        if execution.status not in ("pending", "running"):
            raise ValueError(f"执行任务状态为 {execution.status}，无法取消")

        execution.status = "cancelled"
        execution.end_time = datetime.now()
        await self.db.flush()

        # 标记剩余待执行的用例为跳过
        remaining = await self._get_pending_results(execution_id)
        for r in remaining:
            r.status = "skipped"
            r.error_message = "执行任务已取消"
        await self.db.flush()

        # 广播取消
        await manager.broadcast(execution_id, {
            "type": "execution_cancelled",
            "data": execution.to_dict(),
        })

        logger.info(f"执行已取消: {execution_id}")
        return True

    async def delete_execution(self, execution_id: str) -> bool:
        """
        删除执行记录（级联删除关联的用例结果，不影响测试用例）

        执行记录与测试用例是独立的：删除执行记录只丢失本次执行的
        结果快照，用例定义本身（TestCase）不受影响，可再次执行。

        Args:
            execution_id (str): 执行记录ID

        Returns:
            bool: 删除是否成功

        Raises:
            ValueError: 执行记录不存在或正在运行中
        """
        execution = await self.get_execution(execution_id)
        if not execution:
            raise ValueError(f"执行记录不存在: {execution_id}")

        # 运行中的执行记录禁止删除，避免影响正在执行的任务
        if execution.status == "running":
            raise ValueError(f"执行记录正在运行中，无法删除: {execution_id}")

        # 删除（ORM 级联自动删除关联的 CaseResult，不影响 TestCase）
        await self.db.delete(execution)
        await self.db.flush()
        logger.info(f"执行记录删除成功: id={execution_id}")
        return True

    async def delete_executions(
        self, execution_ids: List[str]
    ) -> Tuple[int, int, int, List[str]]:
        """
        批量删除执行记录

        Args:
            execution_ids (List[str]): 执行记录ID列表

        Returns:
            Tuple[int, int, int, List[str]]: (成功数, 失败数, 跳过数, 错误信息列表)
        """
        success = 0
        failed = 0
        skipped = 0
        errors: List[str] = []
        for eid in execution_ids:
            try:
                await self.delete_execution(eid)
                success += 1
            except ValueError as e:
                msg = str(e)
                if "运行中" in msg:
                    skipped += 1
                else:
                    failed += 1
                    errors.append(f"{eid}: {msg}")
            except Exception as e:
                failed += 1
                errors.append(f"{eid}: {str(e)}")
        await self.db.commit()
        logger.info(f"批量删除执行记录完成: 成功={success}, 失败={failed}, 跳过={skipped}")
        return success, failed, skipped, errors

    async def get_results(
        self, execution_id: str
    ) -> Tuple[List[CaseResult], int]:
        """
        获取执行结果列表

        Args:
            execution_id (str): 执行记录ID

        Returns:
            Tuple[List[CaseResult], int]: (用例结果列表, 总数)
        """
        query = (
            select(CaseResult)
            .where(CaseResult.execution_id == execution_id)
            .order_by(CaseResult.created_at.asc())
        )
        result = await self.db.execute(query)
        items = list(result.scalars().all())

        return items, len(items)

    async def get_case_result(
        self, execution_id: str, case_id: str
    ) -> Optional[CaseResult]:
        """
        获取单个用例的执行结果

        Args:
            execution_id (str): 执行记录ID
            case_id (str): 用例ID

        Returns:
            Optional[CaseResult]: 用例结果对象
        """
        query = select(CaseResult).where(
            CaseResult.execution_id == execution_id,
            CaseResult.case_id == case_id,
        )
        result = await self.db.execute(query)
        return result.scalar_one_or_none()

    def _create_step_callback(
        self, execution_id: str, case_result: CaseResult
    ) -> Callable[[StepResult], None]:
        """
        创建步骤回调函数

        用于在步骤完成时实时推送状态到 WebSocket 客户端。

        Args:
            execution_id (str): 执行记录ID
            case_result (CaseResult): 当前用例结果

        Returns:
            Callable: 回调函数
        """
        def callback(step_result: StepResult) -> None:
            """步骤完成回调"""
            try:
                # 创建异步任务发送消息
                asyncio.create_task(
                    manager.broadcast(execution_id, {
                        "type": "step_update",
                        "data": {
                            "execution_id": execution_id,
                            "case_id": case_result.case_id,
                            "case_name": case_result.case_name,
                            "step_order": step_result.order,
                            "step_name": step_result.name,
                            "action": step_result.action,
                            "status": step_result.status,
                            "duration": step_result.duration,
                            "screenshot": step_result.screenshot,
                            "log": step_result.log,
                            "error": step_result.error,
                        },
                    })
                )
            except Exception as e:
                logger.error(f"步骤回调执行失败: {str(e)}")

        return callback

    async def _run_security_execution(
        self,
        execution_id: str,
        config: Optional[dict] = None,
    ) -> Execution:
        """
        安全测试专用执行流程

        安全测试用例不使用 Playwright（无浏览器），而是用 requests + 签名算法
        直接调用被测系统 API。本方法：
        1. 从执行记录中取出关联的用例
        2. 加载默认安全测试配置
        3. 用 SecurityExecutor 执行
        4. 把结果写回 CaseResult + VulnerabilityResult

        Args:
            execution_id: 执行记录ID
            config: 执行配置（未使用，保留兼容）

        Returns:
            Execution: 更新后的执行记录
        """
        # 延迟导入，避免循环依赖
        from app.executors.security_executor import SecurityExecutor, SecurityCaseResult
        from app.models.security_config import SecurityConfig
        from app.models.vulnerability_result import VulnerabilityResult

        execution = await self.get_execution(execution_id)
        if not execution:
            raise ValueError(f"执行记录不存在: {execution_id}")

        # 广播执行开始
        await manager.broadcast(execution_id, {
            "type": "execution_start",
            "data": execution.to_dict(),
        })

        # 获取所有待执行的用例结果（按模块分组排序）
        results_query = (
            select(CaseResult)
            .join(TestCase, CaseResult.case_id == TestCase.id)
            .where(
                CaseResult.execution_id == execution_id,
                CaseResult.status == "pending",
            )
            .order_by(TestCase.module_id, TestCase.order, TestCase.created_at)
        )
        results_data = await self.db.execute(results_query)
        case_results = list(results_data.scalars().all())

        # 获取用例定义
        case_ids = [r.case_id for r in case_results]
        cases_query = select(TestCase).where(TestCase.id.in_(case_ids))
        cases_data = await self.db.execute(cases_query)
        cases = list(cases_data.scalars().all())
        cases_map = {c.id: c for c in cases}

        # 加载默认安全测试配置
        cfg_query = select(SecurityConfig).where(SecurityConfig.is_default == True)
        cfg_result = await self.db.execute(cfg_query)
        sec_config = cfg_result.scalar_one_or_none()
        if not sec_config:
            # 没有配置，全部标记失败
            for cr in case_results:
                cr.status = "error"
                cr.error_message = "未找到默认安全测试配置"
            execution.status = "failed"
            execution.end_time = datetime.now()
            await self.db.flush()
            return execution

        client_config = sec_config.to_client_config()

        # 在 async 上下文中预先把 ORM 对象转成纯字典（避免线程池脱钩）
        def _to_plain_steps(steps_val):
            if isinstance(steps_val, str):
                try:
                    parsed = json.loads(steps_val)
                    return parsed if isinstance(parsed, dict) else {}
                except Exception:
                    return {}
            return steps_val if isinstance(steps_val, dict) else {}

        plain_cases = [
            {"id": c.id, "name": c.name, "steps": _to_plain_steps(c.steps)}
            for c in cases
        ]

        # 定义线程安全的同步执行函数
        loop = asyncio.get_event_loop()

        def sync_run_security():
            executor = SecurityExecutor(client_config)
            executor.start()
            results = []
            for i, case_dict in enumerate(plain_cases):
                case_id = case_dict.get("id", "")
                case_name = case_dict.get("name", "")
                logger.info(f"⏳ 安全测试用例 {i+1}/{len(plain_cases)}: {case_name}")
                try:
                    result = executor.execute_case(case_dict)
                except Exception as e:
                    logger.error(f"安全用例执行异常: {case_name}, {e}")
                    result = SecurityCaseResult(
                        case_id=case_id, name=case_name,
                        status="error", error=str(e),
                    )
                results.append(result)
            return results

        try:
            results: List[SecurityCaseResult] = await loop.run_in_executor(
                None, sync_run_security
            )
        except Exception as e:
            logger.error(f"安全测试执行异常: {e}")
            execution.status = "failed"
            execution.end_time = datetime.now()
            await self.db.flush()
            return execution

        # 更新用例结果 + 保存漏洞结果
        passed = 0
        failed = 0
        for r in results:
            # 找到对应的 CaseResult 更新状态
            cr = next((c for c in case_results if c.case_id == r.case_id), None)
            if cr:
                if r.status == "error":
                    cr.status = "error"
                    cr.error_message = r.error
                elif r.is_vulnerable:
                    cr.status = "failed"
                    cr.error_message = r.description[:500] if r.description else None
                    failed += 1
                else:
                    cr.status = "passed"
                    passed += 1
                cr.duration = r.duration
                cr.end_time = datetime.now()

            # 保存漏洞测试结果
            vuln_result = VulnerabilityResult(
                id=generate_id("vuln"),
                execution_id=execution_id,
                case_id=r.case_id,
                test_id=r.test_id,
                name=r.name,
                level=r.level,
                description=r.description,
                request_info=r.request_info,
                response_info=r.response_info,
                is_vulnerable=r.is_vulnerable,
                fix_suggestion=r.fix_suggestion,
                duration=r.duration,
                status=r.status,
                error_message=r.error,
                extra_data=r.metadata,
            )
            self.db.add(vuln_result)

        # 更新执行统计
        execution.passed = passed
        execution.failed = failed
        execution.skipped = len(case_results) - passed - failed
        execution.total_cases = len(case_results)
        execution.status = "completed"
        execution.end_time = datetime.now()
        execution.duration = (execution.end_time - execution.start_time).total_seconds() if execution.start_time else 0
        execution.pass_rate = round((passed / execution.total_cases) * 100, 2) if execution.total_cases > 0 else 0

        await self.db.flush()

        # 广播执行完成
        await manager.broadcast(execution_id, {
            "type": "execution_complete",
            "data": execution.to_dict(),
        })

        logger.info(f"安全测试执行完成: {execution_id}, 通过: {passed}, 失败: {failed}")
        return execution

    async def _count_results(self, execution_id: str, status: str) -> int:
        """
        统计指定状态的用例结果数量

        Args:
            execution_id (str): 执行记录ID
            status (str): 状态

        Returns:
            int: 数量
        """
        query = select(func.count(CaseResult.id)).where(
            CaseResult.execution_id == execution_id,
            CaseResult.status == status,
        )
        result = await self.db.execute(query)
        return result.scalar() or 0

    async def _get_pending_results(
        self, execution_id: str
    ) -> List[CaseResult]:
        """
        获取待执行的用例结果

        Args:
            execution_id (str): 执行记录ID

        Returns:
            List[CaseResult]: 待执行的用例结果列表
        """
        query = select(CaseResult).where(
            CaseResult.execution_id == execution_id,
            CaseResult.status.in_(["pending", "running"]),
        )
        result = await self.db.execute(query)
        return list(result.scalars().all())