#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称：functional_report_service.py
模块描述：功能测试报告核心服务，编排 Excel 读取 → 数据分析 → 图表生成 → 报告生成 完整流程

作者：czj
创建日期：2026-08-12
最后修改：2026-08-12
"""

import json
import logging
import uuid
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional, Tuple

from sqlalchemy import select

from app.database import async_session_maker
from app.models import ReportTemplate

from .functional.excel_reader import read_test_cases, read_bug_list, TestCase, Bug
from .functional.data_analyzer import (
    analyze_test_cases,
    analyze_bugs,
    link_bugs_to_cases,
    TestCaseAnalysis,
    BugAnalysis,
)
from .functional.data_extractor import (
    extract_testers,
    extract_test_lead,
    extract_server_info,
    extract_version_info_from_bug_list,
    extract_deployment_env,
)
from .functional.chart_generator import generate_all_charts
from .functional.word_generator import generate_report as generate_word_report
from .functional.markdown_generator import generate_markdown_report
from .functional.config import (
    get_report_filename,
    REPORT_FORMAT_DOCX,
    REPORT_FORMAT_MD,
)


logger = logging.getLogger(__name__)


class FunctionalReportService:
    """
    功能测试报告服务

    提供完整的报告生成流程：上传Excel → 分析数据 → 生成图表 → 生成报告
    """

    def __init__(self, report_base_dir: str):
        """
        Args:
            report_base_dir: 报告存储基础目录（如 data/reports/functional/）
        """
        self._base_dir = Path(report_base_dir)
        self._base_dir.mkdir(parents=True, exist_ok=True)

    # ==================== 公共方法 ====================

    def save_uploaded_file(self, file_content: bytes, filename: str, session_id: str) -> str:
        """
        保存上传的Excel文件到会话目录

        Args:
            file_content: 文件二进制内容
            filename: 文件名
            session_id: 会话ID

        Returns:
            保存的文件路径
        """
        session_dir = self._get_session_dir(session_id)
        file_path = session_dir / filename
        file_path.write_bytes(file_content)
        logger.info(f"文件已保存: {file_path}")
        return str(file_path)

    async def _load_template_config(self, template_id: Optional[int] = None) -> Optional[Dict]:
        """
        从数据库加载报告模板配置

        模板配置会与系统默认配置合并，保证结构完整（缺失键回退到默认值）。
        无可用模板（指定ID不存在或系统中没有任何模板）时返回 None，
        生成器将回退到系统硬编码默认值。

        Args:
            template_id: 模板ID（None 时使用默认模板，无默认模板则返回 None）

        Returns:
            Optional[Dict]: 合并后的模板配置字典，无可用模板时返回 None
        """
        try:
            from .functional.config import get_default_config, merge_template_config

            async with async_session_maker() as session:
                if template_id is not None:
                    template = await session.get(ReportTemplate, template_id)
                    if template is None:
                        logger.warning(f"指定模板不存在: {template_id}，使用系统默认配置")
                        return None
                else:
                    result = await session.execute(
                        select(ReportTemplate).where(ReportTemplate.is_default == True)  # noqa: E712
                    )
                    template = result.scalar_one_or_none()
                    if template is None:
                        logger.info("系统中没有报告模板，使用系统默认配置")
                        return None

                template_config = json.loads(template.config) if template.config else {}
                merged = merge_template_config(get_default_config(), template_config)
                logger.info(f"使用报告模板: {template.name} (id={template.id}, is_default={template.is_default})")
                return merged
        except Exception as e:
            logger.error(f"加载报告模板失败，使用系统默认配置: {e}")
            return None

    async def analyze_and_generate(
        self,
        testcase_path: str,
        buglist_path: str,
        project_name: str,
        report_format: str = "docx",
        session_id: str = "",
        template_id: Optional[int] = None,
    ) -> Dict:
        """
        执行完整分析并生成报告

        Args:
            testcase_path: 测试用例Excel文件路径
            buglist_path: BUG列表Excel文件路径
            project_name: 项目名称
            report_format: 报告格式（docx/md/both）
            session_id: 会话ID（沿用上传的session，保证报告与上传文件在同一目录）
            template_id: 报告模板ID（可选，None 时使用默认模板，无模板时使用系统硬编码默认值）

        Returns:
            {
                "session_id": str,
                "project_name": str,
                "case_analysis": dict,
                "bug_analysis": dict,
                "case_bug_link_count": int,
                "charts": dict,
                "reports": list[str],
            }
        """
        # 0. 加载报告模板配置（如指定模板ID或存在默认模板）
        template_config = await self._load_template_config(template_id)

        # 1. 读取Excel数据
        logger.info("步骤1: 读取测试数据...")
        test_cases = read_test_cases(testcase_path)
        logger.info(f"  [OK] 读取到 {len(test_cases)} 条测试用例")

        bugs = read_bug_list(buglist_path)
        logger.info(f"  [OK] 读取到 {len(bugs)} 条BUG记录")

        if not project_name and bugs:
            project_name = bugs[0].project_name
        if not project_name:
            project_name = "未命名项目"

        # 2. 分析数据
        logger.info("步骤2: 分析测试数据...")
        case_analysis = analyze_test_cases(test_cases)
        bug_analysis = analyze_bugs(bugs)
        case_bug_link = link_bugs_to_cases(bugs, test_cases)
        logger.info(f"  [OK] 用例数: {case_analysis.total_cases}, 通过率: {case_analysis.pass_rate}%")
        logger.info(f"  [OK] BUG总数: {bug_analysis.total_bugs}, 遗留: {bug_analysis.remaining_bugs}")
        logger.info(f"  [OK] 关联了 {len(case_bug_link)} 个用例到BUG")

        # 3. 生成图表
        session_id = session_id or self._generate_session_id()
        charts_dir = self._get_charts_dir(session_id)
        logger.info("步骤3: 生成统计图表...")
        chart_paths = generate_all_charts(case_analysis, bug_analysis, str(charts_dir), template_config)
        logger.info(f"  [OK] 生成了 {len(chart_paths)} 个图表")

        # 4. 生成报告
        reports_dir = self._get_reports_dir(session_id)
        formats_to_generate = ["docx", "md"] if report_format == "both" else [report_format]
        generated_reports = []

        for fmt in formats_to_generate:
            logger.info(f"步骤4: 生成{fmt.upper()}报告...")
            output_file = str(reports_dir / get_report_filename(project_name, format=fmt))

            if fmt == "docx":
                report_path = generate_word_report(
                    template_path="",
                    case_analysis=case_analysis,
                    bug_analysis=bug_analysis,
                    case_bug_link=case_bug_link,
                    chart_paths=chart_paths,
                    output_path=output_file,
                    project_name=project_name,
                    cases=test_cases,
                    bugs=bugs,
                    template_config=template_config,
                )
            else:
                report_path = generate_markdown_report(
                    template_path="",
                    case_analysis=case_analysis,
                    bug_analysis=bug_analysis,
                    case_bug_link=case_bug_link,
                    chart_paths=chart_paths,
                    output_path=output_file,
                    project_name=project_name,
                    cases=test_cases,
                    bugs=bugs,
                    template_config=template_config,
                )
            generated_reports.append(report_path)
            logger.info(f"  [OK] {fmt.upper()}报告已生成: {report_path}")

        return {
            "session_id": session_id,
            "project_name": project_name,
            "case_analysis": self._case_analysis_to_dict(case_analysis),
            "bug_analysis": self._bug_analysis_to_dict(bug_analysis),
            "case_bug_link_count": len(case_bug_link),
            "charts": chart_paths,
            "reports": generated_reports,
        }

    def get_report_info(self, session_id: str) -> Dict:
        """
        获取会话的报告信息

        Args:
            session_id: 会话ID

        Returns:
            报告信息字典
        """
        session_dir = self._get_session_dir(session_id)
        reports_dir = session_dir / "reports"
        charts_dir = session_dir / "charts"

        reports = []
        if reports_dir.exists():
            for f in sorted(reports_dir.iterdir()):
                if f.is_file():
                    reports.append({
                        "name": f.name,
                        "path": str(f),
                        "size": f.stat().st_size,
                    })

        charts = []
        if charts_dir.exists():
            for f in sorted(charts_dir.iterdir()):
                if f.is_file():
                    charts.append({
                        "name": f.name,
                        "path": str(f),
                    })

        # 读取分析结果缓存
        analysis_path = session_dir / "analysis_result.json"
        analysis_result = {}
        if analysis_path.exists():
            import json
            try:
                analysis_result = json.loads(analysis_path.read_text(encoding="utf-8"))
            except Exception:
                pass

        return {
            "session_id": session_id,
            "reports": reports,
            "charts": charts,
            "analysis": analysis_result,
        }

    # ==================== 内部方法 ====================

    def _get_session_dir(self, session_id: str) -> Path:
        """获取会话目录"""
        session_dir = self._base_dir / session_id
        session_dir.mkdir(parents=True, exist_ok=True)
        return session_dir

    def _get_charts_dir(self, session_id: str) -> Path:
        """获取图表目录"""
        charts_dir = self._get_session_dir(session_id) / "charts"
        charts_dir.mkdir(parents=True, exist_ok=True)
        return charts_dir

    def _get_reports_dir(self, session_id: str) -> Path:
        """获取报告目录"""
        reports_dir = self._get_session_dir(session_id) / "reports"
        reports_dir.mkdir(parents=True, exist_ok=True)
        return reports_dir

    def _generate_session_id(self) -> str:
        """生成唯一会话ID"""
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        short_uuid = uuid.uuid4().hex[:8]
        return f"func_{timestamp}_{short_uuid}"

    @staticmethod
    def _case_analysis_to_dict(analysis: TestCaseAnalysis) -> Dict:
        """将TestCaseAnalysis转为可序列化字典"""
        module_stats = {}
        for name, stat in analysis.module_stats.items():
            module_stats[name] = {
                "module_name": stat.module_name,
                "total_cases": stat.total_cases,
                "passed_cases": stat.passed_cases,
                "failed_cases": stat.failed_cases,
                "unverified_cases": stat.unverified_cases,
                "undeveloped_cases": stat.undeveloped_cases,
                "pass_rate": stat.pass_rate,
            }

        return {
            "total_cases": analysis.total_cases,
            "executed_cases": analysis.executed_cases,
            "passed_cases": analysis.passed_cases,
            "failed_cases": analysis.failed_cases,
            "unverified_cases": analysis.unverified_cases,
            "undeveloped_cases": analysis.undeveloped_cases,
            "pass_rate": analysis.pass_rate,
            "execution_rate": analysis.execution_rate,
            "module_stats": module_stats,
        }

    @staticmethod
    def _bug_analysis_to_dict(analysis: BugAnalysis) -> Dict:
        """将BugAnalysis转为可序列化字典"""
        return {
            "total_bugs": analysis.total_bugs,
            "remaining_bugs": analysis.remaining_bugs,
            "closed_bugs": analysis.closed_bugs,
            "level_distribution": analysis.level_distribution,
            "status_distribution": analysis.status_distribution,
            "type_distribution": analysis.type_distribution,
            "module_distribution": analysis.module_distribution,
            "remaining_by_level": analysis.remaining_by_level,
        }