#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称：report_service.py
模块描述：报告生成服务，负责生成 HTML 测试报告

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

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

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

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.config import settings

logger = logging.getLogger(__name__)

# HTML 报告模板
REPORT_TEMPLATE = """<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>测试执行报告 - {execution_name}</title>
    <style>
        * {{ margin: 0; padding: 0; box-sizing: border-box; }}
        body {{
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
                         "Microsoft YaHei", sans-serif;
            background: #f5f7fa;
            color: #303133;
            line-height: 1.6;
        }}
        .container {{ max-width: 900px; margin: 0 auto; padding: 40px 20px; }}

        /* 头部 */
        .header {{
            background: linear-gradient(135deg, #409eff, #66b1ff);
            color: white;
            padding: 32px 40px;
            border-radius: 12px;
            margin-bottom: 24px;
        }}
        .header h1 {{ font-size: 28px; margin-bottom: 8px; }}
        .header .meta {{ font-size: 14px; opacity: 0.9; }}

        /* 统计卡片 */
        .stats-grid {{
            display: grid;
            grid-template-columns: repeat(4, 1fr);
            gap: 16px;
            margin-bottom: 24px;
        }}
        .stat-card {{
            background: white;
            border-radius: 10px;
            padding: 24px;
            text-align: center;
            box-shadow: 0 2px 8px rgba(0,0,0,0.06);
        }}
        .stat-card .stat-value {{
            font-size: 36px;
            font-weight: 700;
            margin-bottom: 4px;
        }}
        .stat-card .stat-label {{ font-size: 14px; color: #909399; }}
        .stat-passed .stat-value {{ color: #67c23a; }}
        .stat-failed .stat-value {{ color: #f56c6c; }}
        .stat-skpped .stat-value {{ color: #909399; }}
        .stat-rate .stat-value {{ color: #409eff; }}

        /* 进度条 */
        .progress-card {{
            background: white;
            border-radius: 10px;
            padding: 24px;
            margin-bottom: 24px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.06);
        }}
        .progress-card h3 {{ margin-bottom: 16px; }}

        /* 执行模块汇总 */
        .module-summary-card {{
            background: white;
            border-radius: 10px;
            padding: 24px;
            margin-bottom: 24px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.06);
        }}
        .module-summary-card h3 {{ margin-bottom: 16px; }}
        .module-summary-grid {{
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
            gap: 12px;
        }}
        .module-summary-item {{
            border: 1px solid #ebeef5;
            border-radius: 8px;
            padding: 12px 16px;
            background: #fafafa;
        }}
        .module-summary-item .module-name {{ font-weight: 600; font-size: 14px; }}
        .module-summary-item .module-stats {{ font-size: 13px; color: #909399; margin-top: 4px; }}

        /* 模块分组标题 */
        .module-section-title {{
            font-size: 15px;
            font-weight: 600;
            color: #303133;
            padding: 12px 16px;
            background: #ecf5ff;
            border-left: 4px solid #409eff;
            border-radius: 4px 4px 0 0;
        }}
        .module-section-title .module-section-stats {{
            font-size: 13px;
            font-weight: 400;
            color: #909399;
            margin-left: 12px;
        }}
        .progress-bar {{
            height: 24px;
            background: #ebeef5;
            border-radius: 12px;
            overflow: hidden;
            display: flex;
        }}
        .progress-passed {{ background: #67c23a; }}
        .progress-failed {{ background: #f56c6c; }}
        .progress-skpped {{ background: #c0c4cc; }}

        /* 结果列表 */
        .results-card {{
            background: white;
            border-radius: 10px;
            padding: 24px;
            margin-bottom: 24px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.06);
        }}
        .results-card h3 {{ margin-bottom: 16px; }}
        .result-item {{
            border: 1px solid #ebeef5;
            border-radius: 8px;
            margin-bottom: 12px;
            overflow: hidden;
        }}
        .result-header {{
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 12px 16px;
            background: #fafafa;
            cursor: pointer;
        }}
        .result-header:hover {{ background: #f0f2f5; }}
        .result-name {{ font-weight: 500; }}
        .result-meta {{ display: flex; gap: 12px; align-items: center; }}
        .badge {{
            padding: 2px 10px;
            border-radius: 10px;
            font-size: 12px;
            font-weight: 500;
        }}
        .badge-passed {{ background: #f0f9eb; color: #67c23a; }}
        .badge-failed {{ background: #fef0f0; color: #f56c6c; }}
        .badge-skpped {{ background: #f4f4f5; color: #909399; }}

        .result-details {{
            padding: 16px;
            border-top: 1px solid #ebeef5;
            display: none;
        }}
        .result-details.open {{ display: block; }}

        /* 步骤表 */
        .steps-table {{
            width: 100%;
            border-collapse: collapse;
            font-size: 13px;
            margin-top: 8px;
        }}
        .steps-table th, .steps-table td {{
            padding: 8px 12px;
            text-align: left;
            border-bottom: 1px solid #ebeef5;
        }}
        .steps-table th {{
            background: #fafafa;
            font-weight: 500;
            color: #606266;
        }}
        .steps-table .step-passed {{ color: #67c23a; }}
        .steps-table .step-failed {{ color: #f56c6c; }}
        .steps-table .step-error {{
            font-size: 12px;
            color: #f56c6c;
            max-width: 300px;
            word-break: break-all;
        }}

        /* 页脚 */
        .footer {{
            text-align: center;
            padding: 24px;
            color: #909399;
            font-size: 13px;
        }}

        @media print {{
            body {{ background: white; }}
            .container {{ max-width: 100%; padding: 20px; }}
            .result-details {{ display: block !important; }}
        }}
    </style>
    <script>
        function toggleResult(id) {{
            var el = document.getElementById('result-' + id);
            if (el) el.classList.toggle('open');
        }}
    </script>
</head>
<body>
    <div class="container">
        <!-- 头部 -->
        <div class="header">
            <h1>{execution_name}</h1>
            <div class="meta">
                执行时间: {start_time} - {end_time} | 总耗时: {duration}s |
                触发方式: {trigger_type} | 触发人: {trigger_by} |
                被测系统: {sut_url}
            </div>
        </div>

        <!-- 统计卡片 -->
        <div class="stats-grid">
            <div class="stat-card stat-passed">
                <div class="stat-value">{passed}</div>
                <div class="stat-label">通过</div>
            </div>
            <div class="stat-card stat-failed">
                <div class="stat-value">{failed}</div>
                <div class="stat-label">失败</div>
            </div>
            <div class="stat-card stat-skpped">
                <div class="stat-value">{skipped}</div>
                <div class="stat-label">跳过</div>
            </div>
            <div class="stat-card stat-rate">
                <div class="stat-value">{pass_rate}%</div>
                <div class="stat-label">通过率</div>
            </div>
        </div>

        <!-- 进度条 -->
        <div class="progress-card">
            <h3>执行进度</h3>
            <div class="progress-bar">
                <div class="progress-passed" style="width: {passed_percent}%;" title="通过 {passed}"></div>
                <div class="progress-failed" style="width: {failed_percent}%;" title="失败 {failed}"></div>
                <div class="progress-skpped" style="width: {skipped_percent}%;" title="跳过 {skipped}"></div>
            </div>
        </div>

        <!-- 执行模块汇总 -->
        <div class="module-summary-card">
            <h3>执行模块</h3>
            {module_summary_html}
        </div>

        <!-- 用例结果 -->
        <div class="results-card">
            <h3>用例执行结果</h3>
            {case_results_html}
        </div>

        <!-- 页脚 -->
        <div class="footer">
            <p>由 平台自动化测试系统 生成 | 生成时间: {generated_at}</p>
        </div>
    </div>
</body>
</html>"""

CASE_RESULT_TEMPLATE = """
<div class="result-item">
    <div class="result-header" onclick="toggleResult('{case_id}')">
        <span class="result-name">{case_name}</span>
        <div class="result-meta">
            <span>{duration}s</span>
            <span class="badge badge-{status_css}">{status_text}</span>
        </div>
    </div>
    <div class="result-details" id="result-{case_id}">
        <table class="steps-table">
            <thead>
                <tr>
                    <th>#</th>
                    <th>步骤名称</th>
                    <th>动作</th>
                    <th>状态</th>
                    <th>耗时</th>
                    <th>错误</th>
                </tr>
            </thead>
            <tbody>
                {steps_html}
            </tbody>
        </table>
    </div>
</div>"""

STEP_TEMPLATE = """
<tr>
    <td>{order}</td>
    <td>{name}</td>
    <td>{action}</td>
    <td class="step-{status_css}">{status_text}</td>
    <td>{duration}s</td>
    <td class="step-error">{error}</td>
</tr>"""

# 执行模块汇总项
MODULE_SUMMARY_ITEM_TEMPLATE = """
<div class="module-summary-item">
    <div class="module-name">{module_name}</div>
    <div class="module-stats">
        通过 <span style="color:#67c23a;font-weight:600;">{passed}</span>
        / 失败 <span style="color:#f56c6c;font-weight:600;">{failed}</span>
        / 共 {total}
    </div>
</div>"""

# 模块分组标题（每个模块一组）
MODULE_SECTION_TEMPLATE = """
<div class="module-section-title">
    {module_name}
    <span class="module-section-stats">
        通过 {passed} / 失败 {failed} / 跳过 {skipped} / 共 {total}
    </span>
</div>
{cases_html}"""


class ReportService:
    """
    报告生成服务

    负责根据执行结果生成格式化的 HTML 测试报告。

    Attributes:
        report_dir (Path): 报告输出目录

    Example:
        >>> service = ReportService()
        >>> html = await service.generate_html(execution_id, db)
        >>> file_path = await service.save_report(execution_id, html)
    """

    def __init__(self, report_dir: Optional[str] = None):
        """
        初始化报告服务

        Args:
            report_dir (Optional[str]): 报告输出目录
        """
        self.report_dir = Path(report_dir or settings.REPORT_DIR)
        self.report_dir.mkdir(parents=True, exist_ok=True)

    async def generate_html(
        self,
        execution_id: str,
        db: AsyncSession,
    ) -> str:
        """
        生成 HTML 测试报告

        Args:
            execution_id (str): 执行记录ID
            db (AsyncSession): 数据库会话

        Returns:
            str: HTML 报告内容
        """
        # 获取执行记录
        query = select(Execution).where(Execution.id == execution_id)
        result = await db.execute(query)
        execution = result.scalar_one_or_none()

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

        # 获取用例结果
        results_query = (
            select(CaseResult)
            .where(CaseResult.execution_id == execution_id)
            .order_by(CaseResult.created_at.asc())
        )
        results_result = await db.execute(results_query)
        case_results = list(results_result.scalars().all())

        # 构建 用例ID → 模块名 映射（用于按模块分组展示）
        module_map: dict = {}
        case_ids = [cr.case_id for cr in case_results if cr.case_id]
        if case_ids:
            cases_q = await db.execute(
                select(TestCase).where(TestCase.id.in_(case_ids))
            )
            cases_data = list(cases_q.scalars().all())
            module_ids = [c.module_id for c in cases_data if c.module_id]
            name_map: dict = {}
            if module_ids:
                mods_q = await db.execute(
                    select(Module).where(Module.id.in_(module_ids))
                )
                name_map = {m.id: m.name for m in mods_q.scalars().all()}
            module_map = {
                c.id: name_map.get(c.module_id, c.module_id or "未分组")
                for c in cases_data
            }

        # 生成用例结果 HTML（按模块分组）与执行模块汇总
        case_results_html = self._render_case_results_grouped(case_results, module_map)
        module_summary_html = self._render_module_summary(case_results, module_map)

        # 计算统计数据
        total = len(case_results)
        passed = sum(1 for r in case_results if r.status == "passed")
        failed = sum(1 for r in case_results if r.status == "failed")
        skipped = sum(1 for r in case_results if r.status == "skipped")

        pass_rate = round(passed / total * 100, 1) if total > 0 else 0.0
        passed_percent = round(passed / total * 100, 1) if total > 0 else 0
        failed_percent = round(failed / total * 100, 1) if total > 0 else 0
        skipped_percent = round(skipped / total * 100, 1) if total > 0 else 0

        # 填充模板
        html = REPORT_TEMPLATE.format(
            execution_name=execution.name,
            start_time=(
                execution.start_time.strftime("%Y-%m-%d %H:%M:%S")
                if execution.start_time else "N/A"
            ),
            end_time=(
                execution.end_time.strftime("%Y-%m-%d %H:%M:%S")
                if execution.end_time else "N/A"
            ),
            duration=f"{execution.duration:.2f}" if execution.duration else "N/A",
            trigger_type=execution.trigger_type,
            trigger_by=execution.trigger_by,
            sut_url=settings.TARGET_URL or "N/A",
            passed=passed,
            failed=failed,
            skipped=skipped,
            pass_rate=pass_rate,
            passed_percent=passed_percent,
            failed_percent=failed_percent,
            skipped_percent=skipped_percent,
            module_summary_html=module_summary_html,
            case_results_html=case_results_html,
            generated_at=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
        )

        return html

    def _render_case_results(self, case_results: List[CaseResult]) -> str:
        """渲染用例结果 HTML"""
        items = []
        for cr in case_results:
            steps_html = self._render_steps(cr.steps_result or [])
            items.append(CASE_RESULT_TEMPLATE.format(
                case_id=cr.case_id,
                case_name=cr.case_name,
                duration=f"{cr.duration:.2f}" if cr.duration else "0",
                status_css=cr.status,
                status_text=self._get_status_text(cr.status),
                steps_html=steps_html,
            ))

        if not items:
            return '<div style="padding: 24px; text-align: center; color: #909399;">暂无执行结果</div>'

        return "\n".join(items)

    def _group_by_module(
        self,
        case_results: List[CaseResult],
        module_map: dict,
    ) -> List[tuple]:
        """
        将用例结果按模块分组（保持首次出现顺序）

        Args:
            case_results (List[CaseResult]): 用例结果列表
            module_map (dict): 用例ID → 模块名映射

        Returns:
            List[tuple]: [(module_name, [case_results])]
        """
        grouped: dict = {}
        for cr in case_results:
            module_name = module_map.get(cr.case_id, "未分组")
            grouped.setdefault(module_name, []).append(cr)
        return list(grouped.items())

    def _render_module_summary(
        self,
        case_results: List[CaseResult],
        module_map: dict,
    ) -> str:
        """
        渲染「执行模块」汇总卡片

        Args:
            case_results (List[CaseResult]): 用例结果列表
            module_map (dict): 用例ID → 模块名映射

        Returns:
            str: 模块汇总 HTML
        """
        groups = self._group_by_module(case_results, module_map)
        if not groups:
            return '<div style="padding: 12px; text-align: center; color: #909399;">暂无模块数据</div>'

        items = []
        for module_name, results in groups:
            passed = sum(1 for r in results if r.status == "passed")
            failed = sum(1 for r in results if r.status == "failed")
            items.append(MODULE_SUMMARY_ITEM_TEMPLATE.format(
                module_name=module_name,
                passed=passed,
                failed=failed,
                total=len(results),
            ))
        return f'<div class="module-summary-grid">{"".join(items)}</div>'

    def _render_case_results_grouped(
        self,
        case_results: List[CaseResult],
        module_map: dict,
    ) -> str:
        """
        按模块分组渲染用例结果

        Args:
            case_results (List[CaseResult]): 用例结果列表
            module_map (dict): 用例ID → 模块名映射

        Returns:
            str: 用例结果 HTML
        """
        if not case_results:
            return '<div style="padding: 24px; text-align: center; color: #909399;">暂无执行结果</div>'

        groups = self._group_by_module(case_results, module_map)
        sections = []
        for module_name, results in groups:
            passed = sum(1 for r in results if r.status == "passed")
            failed = sum(1 for r in results if r.status == "failed")
            skipped = sum(1 for r in results if r.status == "skipped")
            cases_html = self._render_case_items(results)
            sections.append(MODULE_SECTION_TEMPLATE.format(
                module_name=module_name,
                passed=passed,
                failed=failed,
                skipped=skipped,
                total=len(results),
                cases_html=cases_html,
            ))
        return "\n".join(sections)

    def _render_case_items(self, case_results: List[CaseResult]) -> str:
        """渲染单个模块下的用例结果项"""
        items = []
        for cr in case_results:
            steps_html = self._render_steps(cr.steps_result or [])
            items.append(CASE_RESULT_TEMPLATE.format(
                case_id=cr.case_id,
                case_name=cr.case_name,
                duration=f"{cr.duration:.2f}" if cr.duration else "0",
                status_css=cr.status,
                status_text=self._get_status_text(cr.status),
                steps_html=steps_html,
            ))
        return "\n".join(items)

    def _render_steps(self, steps: list) -> str:
        """渲染步骤 HTML"""
        if not steps:
            return '<tr><td colspan="6" style="text-align:center;color:#909399;">暂无步骤</td></tr>'

        items = []
        for step in steps:
            items.append(STEP_TEMPLATE.format(
                order=step.get("order", "-"),
                name=step.get("name", step.get("action", "-")),
                action=step.get("action", "-"),
                status_css=step.get("status", "pending"),
                status_text=self._get_status_text(step.get("status", "pending")),
                duration=f"{step.get('duration', 0):.2f}" if isinstance(step.get('duration'), float) else step.get('duration', '0'),
                error=step.get("error") or "-",
            ))

        return "\n".join(items)

    def _get_status_text(self, status: str) -> str:
        """获取状态文本"""
        status_map = {
            "passed": "通过",
            "failed": "失败",
            "skipped": "跳过",
            "running": "执行中",
            "pending": "待执行",
        }
        return status_map.get(status, status)

    async def save_report(
        self,
        execution_id: str,
        html: str,
    ) -> str:
        """
        保存报告到文件

        Args:
            execution_id (str): 执行记录ID
            html (str): HTML 报告内容

        Returns:
            str: 报告文件路径
        """
        file_name = f"report_{execution_id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.html"
        file_path = self.report_dir / file_name

        file_path.write_text(html, encoding="utf-8")
        logger.info(f"报告已保存: {file_path}")

        return str(file_path)

    async def get_report_path(self, execution_id: str) -> Optional[str]:
        """
        查找已存在的报告文件

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

        Returns:
            Optional[str]: 报告文件路径，不存在则返回 None
        """
        pattern = f"report_{execution_id}_*.html"
        files = sorted(
            self.report_dir.glob(pattern),
            key=lambda p: p.stat().st_mtime,
            reverse=True,
        )
        return str(files[0]) if files else None
