#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称：case_service.py
模块描述：测试用例业务服务，负责用例的增删改查和导入导出

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

import json
import logging
from typing import Optional, List, Tuple, Any, Dict

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

from app.models.test_case import TestCase
from app.models.module import Module
from app.schemas.test_case import (
    TestCaseCreate,
    TestCaseUpdate,
    TestCaseImport,
    StepDefinition,
)
from app.utils.id_generator import generate_id

logger = logging.getLogger(__name__)


class CaseService:
    """
    测试用例服务类

    负责测试用例的业务逻辑处理，包括增删改查、导入导出、搜索筛选。

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

    Example:
        >>> service = CaseService(db)
        >>> case = await service.create(TestCaseCreate(
        ...     module_id="module_001",
        ...     name="正常登录测试",
        ...     steps=[{"order": 1, "action": "navigate", "params": {"url": "/login"}}]
        ... ))
    """

    def __init__(self, db: AsyncSession):
        """
        初始化用例服务

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

        Raises:
            ValueError: 当 db 为 None 时抛出
        """
        if db is None:
            raise ValueError("数据库会话不能为空")
        self.db = db

    async def list(
        self,
        module_id: Optional[str] = None,
        keyword: Optional[str] = None,
        status: Optional[str] = None,
        priority: Optional[str] = None,
        case_type: Optional[str] = None,
        skip: int = 0,
        limit: int = 100,
    ) -> Tuple[List[TestCase], int]:
        """
        获取用例列表（分页、筛选）

        支持按模块、关键词、状态、优先级、类型筛选。

        Args:
            module_id (Optional[str]): 模块ID筛选
            keyword (Optional[str]): 关键词搜索（匹配名称和描述）
            status (Optional[str]): 状态筛选 (active/disabled)
            priority (Optional[str]): 优先级筛选 (high/medium/low)
            case_type (Optional[str]): 用例类型筛选 (ui/api/security/deploy/performance/functional)
            skip (int): 跳过的记录数，默认0
            limit (int): 返回记录数上限，默认100

        Returns:
            Tuple[List[TestCase], int]: (用例列表, 总数)

        Example:
            >>> cases, total = await service.list(
            ...     module_id="module_001",
            ...     keyword="登录",
            ...     case_type="ui",
            ...     skip=0, limit=20
            ... )
        """
        # 构建查询条件
        conditions = []
        if module_id:
            conditions.append(TestCase.module_id == module_id)
        if keyword:
            keyword_like = f"%{keyword}%"
            conditions.append(
                TestCase.name.like(keyword_like) | TestCase.description.like(keyword_like)
            )
        if status:
            conditions.append(TestCase.status == status)
        if priority:
            conditions.append(TestCase.priority == priority)
        if case_type:
            conditions.append(TestCase.case_type == case_type)

        # 查询总数
        count_query = select(func.count(TestCase.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(TestCase)
            .order_by(TestCase.updated_at.desc())
            .offset(skip)
            .limit(limit)
        )
        if conditions:
            query = query.where(*conditions)
        result = await self.db.execute(query)
        cases = list(result.scalars().all())

        return cases, total

    async def get(self, case_id: str) -> Optional[TestCase]:
        """
        获取单个用例详情

        Args:
            case_id (str): 用例ID

        Returns:
            Optional[TestCase]: 用例对象，不存在时返回 None
        """
        query = select(TestCase).where(TestCase.id == case_id)
        result = await self.db.execute(query)
        return result.scalar_one_or_none()

    async def create(self, data: TestCaseCreate) -> TestCase:
        """
        创建测试用例

        根据提供的参数创建新的测试用例。

        Args:
            data (TestCaseCreate): 用例创建数据

        Returns:
            TestCase: 创建成功的用例对象

        Raises:
            ValueError: 当参数校验失败时抛出

        Example:
            >>> case = await service.create(TestCaseCreate(
            ...     module_id="module_001",
            ...     name="正常登录测试",
            ...     steps=[StepDefinition(order=1, name="打开页面", action="navigate", params={"url": "/login"})]
            ... ))
        """
        # 校验模块是否存在
        module_query = select(Module).where(Module.id == data.module_id)
        module_result = await self.db.execute(module_query)
        if not module_result.scalar_one_or_none():
            raise ValueError(f"模块不存在: {data.module_id}")

        # 参数校验
        if not data.name or not data.name.strip():
            raise ValueError("用例名称不能为空")
        if len(data.name) > 200:
            raise ValueError("用例名称不能超过200字符")

        # 步骤校验（仅UI用例需要列表格式校验）
        if data.steps and data.case_type == "ui":
            if isinstance(data.steps, list):
                for i, step in enumerate(data.steps):
                    if not step.action:
                        raise ValueError(f"第{i+1}步的动作类型不能为空")

        # 处理 steps 字段：UI用例转列表，安全测试用例保持字典
        steps_data = []
        if data.steps:
            if isinstance(data.steps, list):
                # UI用例：StepDefinition列表转字典列表
                steps_data = [s.model_dump() for s in data.steps]
            elif isinstance(data.steps, dict):
                # 安全测试用例：直接使用字典
                steps_data = data.steps

        # 创建用例对象
        test_case = TestCase(
            id=generate_id("case"),
            module_id=data.module_id,
            name=data.name.strip(),
            description=data.description or "",
            priority=data.priority or "medium",
            tags=data.tags or [],
            steps=steps_data,
            config=data.config or {"timeout": 30000, "retry": 0, "screenshot": True},
            case_type=data.case_type or "ui",
        )

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

        logger.info(f"用例创建成功: id={test_case.id}, name={test_case.name}")
        return test_case

    async def update(self, case_id: str, data: TestCaseUpdate) -> Optional[TestCase]:
        """
        更新测试用例

        只更新传入的非空字段。

        Args:
            case_id (str): 用例ID
            data (TestCaseUpdate): 更新数据

        Returns:
            Optional[TestCase]: 更新后的用例对象，不存在时返回 None

        Example:
            >>> updated = await service.update("case_001", TestCaseUpdate(name="新名称"))
        """
        test_case = await self.get(case_id)
        if not test_case:
            logger.warning(f"用例不存在: {case_id}")
            return None

        # 如果更新模块，校验新模块是否存在
        if data.module_id and data.module_id != test_case.module_id:
            module_query = select(Module).where(Module.id == data.module_id)
            module_result = await self.db.execute(module_query)
            if not module_result.scalar_one_or_none():
                raise ValueError(f"模块不存在: {data.module_id}")

        # 只更新传入的非空字段
        update_data = data.model_dump(exclude_unset=True)
        for field, value in update_data.items():
            if value is not None:
                setattr(test_case, field, value)

        await self.db.flush()
        await self.db.refresh(test_case)

        logger.info(f"用例更新成功: id={test_case.id}")
        return test_case

    async def delete(self, case_id: str) -> bool:
        """
        删除测试用例

        Args:
            case_id (str): 用例ID

        Returns:
            bool: 删除是否成功
        """
        test_case = await self.get(case_id)
        if not test_case:
            logger.warning(f"用例不存在，无法删除: {case_id}")
            return False

        await self.db.delete(test_case)
        await self.db.flush()

        logger.info(f"用例删除成功: id={case_id}")
        return True

    async def copy(self, case_id: str) -> Optional[TestCase]:
        """
        复制测试用例

        基于现有用例创建一个副本，名称添加"(副本)"后缀。

        Args:
            case_id (str): 源用例ID

        Returns:
            Optional[TestCase]: 复制后的新用例，源不存在时返回 None
        """
        source = await self.get(case_id)
        if not source:
            return None

        new_case = TestCase(
            id=generate_id("case"),
            module_id=source.module_id,
            name=f"{source.name}(副本)",
            description=source.description,
            status=source.status,
            priority=source.priority,
            tags=source.tags.copy() if source.tags else [],
            steps=source.steps.copy() if source.steps else [],
            config=source.config.copy() if source.config else {},
            case_type=source.case_type or "ui",
        )

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

        logger.info(f"用例复制成功: source={case_id}, new_id={new_case.id}")
        return new_case

    async def import_cases(
        self, cases: List[TestCaseImport]
    ) -> Tuple[int, int, List[str]]:
        """
        批量导入测试用例

        从导入数据中批量创建用例，按 module_name 匹配目标模块。

        Args:
            cases (List[TestCaseImport]): 导入的用例数据列表

        Returns:
            Tuple[int, int, List[str]]: (成功数, 失败数, 错误信息列表)

        Example:
            >>> success, failed, errors = await service.import_cases([
            ...     TestCaseImport(name="登录测试", module_name="登录模块", steps=[])
            ... ])
        """
        success_count = 0
        fail_count = 0
        errors: List[str] = []

        for i, case_data in enumerate(cases):
            try:
                # 按模块名称查找模块
                module_query = select(Module).where(Module.name == case_data.module_name)
                module_result = await self.db.execute(module_query)
                module = module_result.scalar_one_or_none()

                if not module:
                    # 自动创建模块
                    module = Module(
                        id=generate_id("module"),
                        name=case_data.module_name,
                        description=f"从导入自动创建 - {case_data.module_name}",
                    )
                    self.db.add(module)
                    await self.db.flush()
                    await self.db.refresh(module)
                    logger.info(f"导入时自动创建模块: {module.name}")

                # 创建用例
                test_case = TestCase(
                    id=generate_id("case"),
                    module_id=module.id,
                    name=case_data.name,
                    description=case_data.description or "",
                    priority=case_data.priority or "medium",
                    tags=case_data.tags or [],
                    steps=[s.model_dump() for s in case_data.steps] if case_data.steps else [],
                    config=case_data.config or {"timeout": 30000, "retry": 0, "screenshot": True},
                    case_type=case_data.case_type or "ui",
                )
                self.db.add(test_case)
                await self.db.flush()
                success_count += 1

            except Exception as e:
                fail_count += 1
                errors.append(f"第{i+1}条导入失败 ({case_data.name}): {str(e)}")
                continue

        if success_count > 0:
            await self.db.flush()

        logger.info(f"用例导入完成: 成功={success_count}, 失败={fail_count}")
        return success_count, fail_count, errors

    async def export_cases(
        self, case_ids: Optional[List[str]] = None
    ) -> List[Dict[str, Any]]:
        """
        导出测试用例

        导出指定ID的用例，如果未指定则导出所有用例。

        Args:
            case_ids (Optional[List[str]]): 要导出的用例ID列表

        Returns:
            List[Dict]: 用例数据列表（JSON 格式）
        """
        query = select(TestCase)
        if case_ids:
            query = query.where(TestCase.id.in_(case_ids))

        result = await self.db.execute(query)
        cases = list(result.scalars().all())

        # 获取模块名称映射
        module_ids = list(set(c.module_id for c in cases))
        module_query = select(Module).where(Module.id.in_(module_ids))
        module_result = await self.db.execute(module_query)
        modules = {m.id: m.name for m in module_result.scalars().all()}

        return [
            {
                "name": c.name,
                "module_name": modules.get(c.module_id, ""),
                "description": c.description,
                "priority": c.priority,
                "tags": c.tags or [],
                "steps": c.steps or [],
                "config": c.config or {},
                "case_type": getattr(c, "case_type", None) or "ui",
            }
            for c in cases
        ]