#!/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; }}
        .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}
            </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="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>"""


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())

        # 生成用例结果 HTML
        case_results_html = self._render_case_results(case_results)

        # 计算统计数据
        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,
            passed=passed,
            failed=failed,
            skipped=skipped,
            pass_rate=pass_rate,
            passed_percent=passed_percent,
            failed_percent=failed_percent,
            skipped_percent=skipped_percent,
            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 _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
