#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称：dependency_service.py
模块描述：用例依赖管理服务

作者：czj
创建日期：2026-07-12
"""

import logging
from typing import Optional, List, Dict, Any, Tuple
from collections import defaultdict, deque

from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, delete
from sqlalchemy.orm import selectinload

from app.models.test_case import TestCase
from app.models.case_dependency import CaseDependency
from app.utils.id_generator import generate_id

logger = logging.getLogger(__name__)


class DependencyService:
    """
    依赖管理服务

    负责测试用例依赖关系的管理，包括：
    - 依赖关系的设置和验证
    - 拓扑排序算法
    - 循环依赖检测
    - 执行顺序计算

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

    Example:
        >>> service = DependencyService(db)
        >>> dependencies = await service.get_case_dependencies("case_001")
        >>> order = await service.topological_sort(["case_001", "case_002", "case_003"])
    """

    def __init__(self, db: AsyncSession):
        """
        初始化依赖管理服务

        Args:
            db (AsyncSession): 数据库会话对象
        """
        if db is None:
            raise ValueError("数据库会话不能为空")
        self.db = db

    async def get_case_dependencies(self, case_id: str) -> List[CaseDependency]:
        """
        获取用例的所有依赖关系

        Args:
            case_id (str): 用例ID

        Returns:
            List[CaseDependency]: 依赖关系列表
        """
        query = (
            select(CaseDependency)
            .where(CaseDependency.case_id == case_id)
            .order_by(CaseDependency.order)
        )
        result = await self.db.execute(query)
        return list(result.scalars().all())

    async def set_case_dependencies(
        self,
        case_id: str,
        dependencies: List[Dict[str, Any]]
    ) -> bool:
        """
        设置用例依赖关系

        先删除现有依赖，再创建新的依赖关系。

        Args:
            case_id (str): 用例ID
            dependencies (List[Dict]): 依赖定义列表
                [{"depends_on_case_id": "case_001", "dependency_type": "hard", "order": 1}]

        Returns:
            bool: 设置是否成功

        Raises:
            ValueError: 当依赖关系无效时抛出
        """
        # 验证依赖用例是否存在
        dep_case_ids = [d["depends_on_case_id"] for d in dependencies]
        if dep_case_ids:
            query = select(TestCase.id).where(TestCase.id.in_(dep_case_ids))
            result = await self.db.execute(query)
            existing_ids = set(result.scalars().all())

            missing_ids = set(dep_case_ids) - existing_ids
            if missing_ids:
                raise ValueError(f"依赖的用例不存在: {missing_ids}")

        # 验证是否包含自依赖
        if case_id in dep_case_ids:
            raise ValueError("用例不能依赖自己")

        # 删除现有依赖
        delete_query = delete(CaseDependency).where(CaseDependency.case_id == case_id)
        await self.db.execute(delete_query)

        # 创建新依赖
        for dep_data in dependencies:
            dependency = CaseDependency(
                id=generate_id("dep"),
                case_id=case_id,
                depends_on_case_id=dep_data["depends_on_case_id"],
                dependency_type=dep_data.get("dependency_type", "hard"),
                order=dep_data.get("order", 0),
            )
            self.db.add(dependency)

        await self.db.flush()
        logger.info(f"设置用例依赖成功: case_id={case_id}, count={len(dependencies)}")
        return True

    async def validate_dependencies(self, case_ids: List[str]) -> Dict[str, Any]:
        """
        验证依赖关系是否有效（检测循环依赖）

        Args:
            case_ids (List[str]): 用例ID列表

        Returns:
            Dict[str, Any]: 验证结果
                {
                    "valid": bool,
                    "cycle": Optional[List[str]],
                    "message": str
                }
        """
        # 构建依赖图
        graph = await self._build_dependency_graph(case_ids)

        # 检测循环
        cycle = self._detect_cycle(graph)

        if cycle:
            return {
                "valid": False,
                "cycle": cycle,
                "message": f"检测到循环依赖: {' -> '.join(cycle)}"
            }
        else:
            return {
                "valid": True,
                "cycle": None,
                "message": "依赖关系有效，无循环"
            }

    async def topological_sort(
        self,
        case_ids: List[str],
        respect_priority: bool = True
    ) -> Tuple[List[str], Optional[List[str]]]:
        """
        拓扑排序用例ID列表（Kahn算法）

        Args:
            case_ids (List[str]): 用例ID列表
            respect_priority (bool): 是否在依赖级别内按优先级排序，默认True

        Returns:
            Tuple[List[str], Optional[List[str]]]: (排序结果, 检测到的循环)
                如果存在循环依赖，第二个元素为循环路径
        """
        if not case_ids:
            return [], None

        # 构建依赖图
        graph = await self._build_dependency_graph(case_ids)

        # 计算入度
        in_degree = {case_id: 0 for case_id in case_ids}
        for case_id, deps in graph.items():
            for dep in deps:
                if dep in in_degree:
                    in_degree[case_id] += 1

        # 零入度队列
        queue = deque([case_id for case_id, degree in in_degree.items() if degree == 0])
        result = []

        # 获取用例优先级（如果需要）
        priority_map = {}
        if respect_priority:
            query = select(TestCase.id, TestCase.priority).where(TestCase.id.in_(case_ids))
            result_db = await self.db.execute(query)
            priority_map = {row[0]: row[1] for row in result_db.all()}

        while queue:
            # 在同一层级内按优先级排序
            if respect_priority and len(queue) > 1:
                # 转换为列表排序后再转回deque
                level_nodes = list(queue)
                level_nodes.sort(key=lambda x: self._priority_to_int(priority_map.get(x, "medium")))
                queue = deque(level_nodes)

            node = queue.popleft()
            result.append(node)

            # 减少依赖节点的入度
            for dependent in graph.get(node, []):
                if dependent in in_degree:
                    in_degree[dependent] -= 1
                    if in_degree[dependent] == 0:
                        queue.append(dependent)

        # 检测循环
        if len(result) != len(case_ids):
            cycle = [case_id for case_id in case_ids if case_id not in result]
            logger.warning(f"检测到循环依赖: {cycle}")
            return result, cycle

        return result, None

    async def get_execution_order(
        self,
        case_ids: List[str]
    ) -> List[str]:
        """
        计算执行顺序（拓扑排序 + 优先级排序）

        1. 先拓扑排序处理依赖
        2. 在依赖级别内按优先级排序
        3. 返回最终执行顺序

        Args:
            case_ids (List[str]): 用例ID列表

        Returns:
            List[str]: 排序后的用例ID列表

        Raises:
            ValueError: 当存在循环依赖时抛出
        """
        sorted_ids, cycle = await self.topological_sort(case_ids, respect_priority=True)

        if cycle:
            raise ValueError(f"存在循环依赖，无法确定执行顺序: {cycle}")

        return sorted_ids

    async def check_dependency_status(
        self,
        case_id: str,
        execution_id: str
    ) -> Dict[str, Any]:
        """
        检查用例依赖状态，判断是否可以执行

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

        Returns:
            Dict[str, Any]: 依赖状态
                {
                    "can_execute": bool,
                    "status": str,  # ok/blocked/skipped
                    "failed_dependencies": List[str],
                    "message": str
                }
        """
        from app.models.case_result import CaseResult

        # 获取依赖关系
        dependencies = await self.get_case_dependencies(case_id)

        if not dependencies:
            return {
                "can_execute": True,
                "status": "ok",
                "failed_dependencies": [],
                "message": "无依赖关系"
            }

        # 检查每个依赖的执行结果
        failed_deps = []
        for dep in dependencies:
            # 查询依赖用例的执行结果
            query = select(CaseResult).where(
                CaseResult.execution_id == execution_id,
                CaseResult.case_id == dep.depends_on_case_id
            )
            result = await self.db.execute(query)
            case_result = result.scalar_one_or_none()

            if not case_result:
                # 依赖用例尚未执行
                if dep.dependency_type == "hard":
                    return {
                        "can_execute": False,
                        "status": "blocked",
                        "failed_dependencies": [dep.depends_on_case_id],
                        "message": f"依赖用例 {dep.depends_on_case_id} 尚未执行"
                    }
            elif case_result.status == "failed":
                # 依赖用例执行失败
                if dep.dependency_type == "hard":
                    failed_deps.append(dep.depends_on_case_id)
            elif case_result.status == "skipped":
                # 依赖用例被跳过
                if dep.dependency_type == "hard":
                    failed_deps.append(dep.depends_on_case_id)

        if failed_deps:
            return {
                "can_execute": False,
                "status": "blocked",
                "failed_dependencies": failed_deps,
                "message": f"依赖用例执行失败或被跳过: {failed_deps}"
            }
        else:
            return {
                "can_execute": True,
                "status": "ok",
                "failed_dependencies": [],
                "message": "所有依赖满足"
            }

    async def _build_dependency_graph(self, case_ids: List[str]) -> Dict[str, List[str]]:
        """
        构建依赖关系图

        Args:
            case_ids (List[str]): 用例ID列表

        Returns:
            Dict[str, List[str]]: 依赖图
                {case_id: [depends_on_case_id_1, depends_on_case_id_2, ...]}
        """
        # 查询所有依赖关系
        query = select(CaseDependency).where(
            CaseDependency.case_id.in_(case_ids)
        )
        result = await self.db.execute(query)
        dependencies = list(result.scalars().all())

        # 构建图
        graph = defaultdict(list)
        for dep in dependencies:
            if dep.depends_on_case_id in case_ids:
                graph[dep.case_id].append(dep.depends_on_case_id)

        # 确保所有case_id都在图中
        for case_id in case_ids:
            if case_id not in graph:
                graph[case_id] = []

        return dict(graph)

    def _detect_cycle(self, graph: Dict[str, List[str]]) -> Optional[List[str]]:
        """
        检测依赖图中的循环（DFS）

        Args:
            graph (Dict[str, List[str]]): 依赖图

        Returns:
            Optional[List[str]]: 检测到的循环路径，无循环时返回None
        """
        visited = set()
        rec_stack = set()
        path = []

        def dfs(node: str) -> Optional[List[str]]:
            visited.add(node)
            rec_stack.add(node)
            path.append(node)

            for neighbor in graph.get(node, []):
                if neighbor not in visited:
                    cycle = dfs(neighbor)
                    if cycle:
                        return cycle
                elif neighbor in rec_stack:
                    # 找到循环
                    cycle_start = path.index(neighbor)
                    return path[cycle_start:] + [neighbor]

            path.pop()
            rec_stack.remove(node)
            return None

        for node in graph:
            if node not in visited:
                cycle = dfs(node)
                if cycle:
                    return cycle

        return None

    def _priority_to_int(self, priority: str) -> int:
        """
        将优先级转换为整数（用于排序）

        Args:
            priority (str): 优先级 (high/medium/low)

        Returns:
            int: 优先级数值（数值越小优先级越高）
        """
        priority_map = {
            "high": 0,
            "medium": 1,
            "low": 2
        }
        return priority_map.get(priority, 1)
