#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称：performance_ai_service.py
模块描述：性能测试报告 AI 分析服务 — 调用 Claude CLI 分析瓶颈/优化建议

核心功能：
1. 单次执行分析：对一次执行报告做瓶颈定位、性能评价、优化建议
2. 多版本对比分析：对多次执行报告做趋势/差异对比分析
3. 规则兜底：Claude 不可用时基于硬编码阈值规则生成分析

作者：czj
创建日期：2026-08-24
"""

import json
import logging
import os
import platform
import re
import subprocess
from typing import Any, Dict, List, Optional

from app.config import settings

logger = logging.getLogger(__name__)

# ==================== 阈值常量 ====================

THRESHOLD_P95_GREEN = 300       # P95 < 300ms → 良好
THRESHOLD_P95_YELLOW = 1000     # P95 < 1000ms → 需关注，≥ 1000ms → 瓶颈
THRESHOLD_APDEX_GREEN = 0.94    # Apdex ≥ 0.94 → 良好
THRESHOLD_APDEX_YELLOW = 0.85   # Apdex ≥ 0.85 → 需关注
THRESHOLD_ERROR_RATE = 5.0      # 错误率 > 5% → 异常
THRESHOLD_CPU_HIGH = 80.0       # CPU 使用率 > 80% → 资源瓶颈
THRESHOLD_MEMORY_HIGH = 80.0    # 内存使用率 > 80% → 资源瓶颈


# ==================== Prompt 模板 ====================

SINGLE_ANALYSIS_PROMPT = """你是一个性能测试专家，分析以下性能测试报告数据，给出详细的瓶颈分析和优化建议。

## 报告数据

### 任务概要
{summary_json}

### 指标统计
{metrics_json}

### 执行机资源
{resource_summary_json}

### 目标机资源
{target_resource_summary_json}

### 事务处理
{transaction_summary_json}

### 接口维度
{api_summary_json}

## 分析要求

请从以下维度进行详细分析：

1. **总体评价**（overall_verdict）：用一段 Markdown 文字综合评价本次压测结果，包括是否达到预期、关键指标表现、整体健康度。

2. **关键发现**（key_findings）：识别出报告中最重要的发现，每个发现包含：
   - type: 发现类型（"异常"|"拐点"|"状态码"|"其他"）
   - description: 发现描述
   - severity: 严重度（"high"|"medium"|"low"）
   - evidence: 数据支撑（引用具体指标值）

3. **瓶颈分析**（bottlenecks）：定位性能瓶颈，每个瓶颈包含：
   - type: 瓶颈类型（"响应时间"|"吞吐量"|"错误率"|"资源"|"事务"|"其他"）
   - location: 瓶颈位置（如"接口 /api/xxx"、"执行机 CPU"、"目标机内存"）
   - severity: 严重度（"high"|"medium"|"low"）
   - description: 瓶颈描述
   - evidence: 数据支撑

4. **优化建议**（suggestions）：给出可操作的优化建议，每项包含：
   - priority: 优先级（"P0"|"P1"|"P2"）
   - content: 建议内容
   - expectedEffect: 预期效果

5. **资源分析**（resource_analysis）：用一段 Markdown 文字分析执行机和目标机的资源使用情况，包括 CPU、内存、负载趋势，以及是否存在资源瓶颈。

## 输出格式

必须返回严格的 JSON 格式（不要包含 markdown 代码块标记），字段如下：
{{
  "overall_verdict": "Markdown 格式的总体评价",
  "key_findings": [
    {{"type": "异常", "description": "...", "severity": "high", "evidence": "..."}}
  ],
  "bottlenecks": [
    {{"type": "响应时间", "location": "...", "severity": "high", "description": "...", "evidence": "..."}}
  ],
  "suggestions": [
    {{"priority": "P0", "content": "...", "expectedEffect": "..."}}
  ],
  "resource_analysis": "Markdown 格式的资源分析"
}}
"""

COMPARISON_ANALYSIS_PROMPT = """你是一个性能测试专家，对比分析以下多次性能测试报告数据，给出趋势分析和优化建议。

## 报告数据（按执行时间先后排列）

{comparison_data_json}

## 分析要求

请从以下维度进行详细分析：

1. **总体评价**（overall_verdict）：用一段 Markdown 文字综合评价各次压测的表现趋势，整体性能是提升还是下降。

2. **关键发现**（key_findings）：识别出对比中最重要发现，每个发现包含：
   - type: 发现类型（"趋势"|"回归"|"改善"|"其他"）
   - description: 发现描述
   - severity: 严重度（"high"|"medium"|"low"）
   - evidence: 数据支撑

3. **瓶颈分析**（bottlenecks）：定位各版本中的性能瓶颈，每个瓶颈包含：
   - type: 瓶颈类型（"响应时间"|"吞吐量"|"错误率"|"资源"|"事务"|"其他"）
   - location: 瓶颈位置
   - severity: 严重度（"high"|"medium"|"low"）
   - description: 瓶颈描述
   - evidence: 数据支撑

4. **优化建议**（suggestions）：给出针对性的优化建议，每项包含：
   - priority: 优先级（"P0"|"P1"|"P2"）
   - content: 建议内容
   - expectedEffect: 预期效果

5. **对比分析**（comparison）：逐指标对比各版本差异，每项包含：
   - metric: 指标名称
   - base_value: 基准值（首次执行）
   - current_value: 当前值（末次执行）
   - change_percent: 变化百分比
   - direction: 变化方向（"improved"|"regressed"|"stable"）
   - assessment: 评价

## 输出格式

必须返回严格的 JSON 格式（不要包含 markdown 代码块标记），字段如下：
{{
  "overall_verdict": "Markdown 格式的总体评价",
  "key_findings": [
    {{"type": "趋势", "description": "...", "severity": "high", "evidence": "..."}}
  ],
  "bottlenecks": [
    {{"type": "响应时间", "location": "...", "severity": "high", "description": "...", "evidence": "..."}}
  ],
  "suggestions": [
    {{"priority": "P0", "content": "...", "expectedEffect": "..."}}
  ],
  "comparison": [
    {{"metric": "TPS", "base_value": 10.5, "current_value": 15.2, "change_percent": 44.8, "direction": "improved", "assessment": "吞吐量显著提升"}}
  ]
}}
"""


class PerformanceAiService:
    """
    性能测试报告 AI 分析服务

    核心功能：
    1. 单次执行分析：对一次执行报告做瓶颈定位、性能评价、优化建议
    2. 多版本对比分析：对多次执行报告做趋势/差异对比分析
    3. 规则兜底：Claude 不可用时基于硬编码阈值规则生成分析
    """

    def __init__(self):
        self.enabled = settings.CLAUDE_ENABLED
        self.timeout = settings.CLAUDE_TIMEOUT
        self.perf_ai_enabled = os.getenv("PERF_AI_ANALYSIS_ENABLED", "true").lower() == "true"
        logger.info(
            f"性能 AI 分析服务初始化: enabled={self.enabled}, "
            f"perf_ai_enabled={self.perf_ai_enabled}, timeout={self.timeout}s"
        )

    def analyze_single(self, report_data: dict) -> dict:
        """
        单次执行 AI 分析

        Args:
            report_data: 报告数据字典（来自 PerformanceService._build_report_from_execution）

        Returns:
            dict: 分析结果（AiAnalysisResponse 兼容格式）
        """
        if not self.enabled or not self.perf_ai_enabled:
            logger.info("AI 分析已禁用，使用规则兜底分析")
            return self._rule_based_analysis(report_data, analysis_type="single")

        prompt = self._build_prompt(report_data, mode="single")
        try:
            response = self._call_claude_cli(prompt)
            result = self._parse_json_response(response)
            result["source"] = "ai"
            logger.info("单次 AI 分析成功")
            return result
        except Exception as e:
            logger.warning(f"Claude 调用失败，回退到规则分析: {e}")
            return self._rule_based_analysis(report_data, analysis_type="single")

    def analyze_comparison(self, report_data_list: list) -> dict:
        """
        多版本对比 AI 分析

        Args:
            report_data_list: 报告数据列表（按执行时间先后排列）

        Returns:
            dict: 分析结果（AiAnalysisResponse 兼容格式）
        """
        if not self.enabled or not self.perf_ai_enabled:
            logger.info("AI 分析已禁用，使用规则兜底对比分析")
            return self._rule_based_comparison(report_data_list)

        prompt = self._build_comparison_prompt(report_data_list)
        try:
            response = self._call_claude_cli(prompt)
            result = self._parse_json_response(response)
            result["source"] = "ai"
            logger.info("对比 AI 分析成功")
            return result
        except Exception as e:
            logger.warning(f"Claude 对比分析调用失败，回退规则分析: {e}")
            return self._rule_based_comparison(report_data_list)

    # ==================== Prompt 构建 ====================

    def _build_prompt(self, report_data: dict, mode: str = "single") -> str:
        """构建单次分析 Prompt"""
        summary = report_data.get("summary", {})
        metrics = report_data.get("metrics", {})
        resource_summary = report_data.get("resource_summary") or {}
        target_resource_summary = report_data.get("target_resource_summary") or {}
        transaction_summary = report_data.get("transaction_summary") or {}
        api_summary = report_data.get("api_summary") or []

        return SINGLE_ANALYSIS_PROMPT.format(
            summary_json=json.dumps(summary, ensure_ascii=False, indent=2, default=str),
            metrics_json=json.dumps(metrics, ensure_ascii=False, indent=2, default=str),
            resource_summary_json=json.dumps(resource_summary, ensure_ascii=False, indent=2, default=str),
            target_resource_summary_json=json.dumps(target_resource_summary, ensure_ascii=False, indent=2, default=str),
            transaction_summary_json=json.dumps(transaction_summary, ensure_ascii=False, indent=2, default=str),
            api_summary_json=json.dumps(api_summary, ensure_ascii=False, indent=2, default=str),
        )

    def _build_comparison_prompt(self, report_data_list: list) -> str:
        """构建对比分析 Prompt"""
        # 简化每份报告的数据，保留关键对比字段
        simplified = []
        for i, report in enumerate(report_data_list):
            summary = report.get("summary", {})
            metrics = report.get("metrics", {})
            simplified.append({
                "index": i + 1,
                "execution_id": summary.get("execution_id", f"execution_{i}"),
                "task_name": summary.get("task_name", ""),
                "start_time": summary.get("start_time", ""),
                "summary": summary,
                "metrics": {
                    "total_requests": metrics.get("total_requests"),
                    "success_count": metrics.get("success_count"),
                    "fail_count": metrics.get("fail_count"),
                    "error_rate": metrics.get("error_rate"),
                    "actual_tps": metrics.get("actual_tps"),
                    "peak_tps": metrics.get("peak_tps"),
                    "avg_response_time": metrics.get("avg_response_time"),
                    "p50_response_time": metrics.get("p50_response_time"),
                    "p95_response_time": metrics.get("p95_response_time"),
                    "p99_response_time": metrics.get("p99_response_time"),
                    "std_dev": metrics.get("std_dev"),
                    "apdex": metrics.get("apdex"),
                    "latency_avg": metrics.get("latency_avg"),
                    "connect_time_avg": metrics.get("connect_time_avg"),
                    "total_received_bytes": metrics.get("total_received_bytes"),
                    "received_kb_per_sec": metrics.get("received_kb_per_sec"),
                    "status_2xx": metrics.get("status_2xx"),
                    "status_4xx": metrics.get("status_4xx"),
                    "status_5xx": metrics.get("status_5xx"),
                },
                "resource_summary": report.get("resource_summary"),
                "target_resource_summary": report.get("target_resource_summary"),
                "api_summary": report.get("api_summary"),
            })

        return COMPARISON_ANALYSIS_PROMPT.format(
            comparison_data_json=json.dumps(simplified, ensure_ascii=False, indent=2, default=str),
        )

    # ==================== Claude CLI 调用（3 级回退）====================

    def _call_claude_cli(self, prompt: str) -> str:
        """
        调用 Claude CLI（3 级回退）

        1. 本地 Claude CLI（Windows/Linux）
        2. SSH 调用宿主机 Claude CLI（Docker 容器内）
        3. paramiko SSH 调用

        Args:
            prompt: Prompt 文本

        Returns:
            str: Claude 响应文本

        Raises:
            Exception: 所有方式均失败时抛出
        """
        env = os.environ.copy()
        env["PYTHONIOENCODING"] = "utf-8"
        env["LANG"] = "en_US.UTF-8"

        is_windows = platform.system() == "Windows"

        # ========== 方式1: 本地 Claude CLI ==========
        try:
            if is_windows:
                result = subprocess.run(
                    "claude --print",
                    input=prompt,
                    capture_output=True,
                    text=True,
                    timeout=self.timeout,
                    shell=True,
                    env=env,
                    encoding="utf-8",
                    errors="replace",
                )
            else:
                result = subprocess.run(
                    ["claude", "--print"],
                    input=prompt,
                    capture_output=True,
                    text=True,
                    timeout=self.timeout,
                    env=env,
                    encoding="utf-8",
                    errors="replace",
                )

            if result.returncode == 0 and result.stdout and result.stdout.strip():
                logger.debug(f"Claude CLI 本地调用成功: {len(result.stdout)} 字符")
                return result.stdout.strip()
        except FileNotFoundError:
            logger.debug("本地 Claude CLI 未找到，尝试其他方式")
        except subprocess.TimeoutExpired:
            logger.warning("本地 Claude CLI 超时，尝试其他方式")
        except Exception as e:
            logger.debug(f"本地 Claude CLI 失败: {e}")

        # ========== 方式2: SSH 调用宿主机 ==========
        try:
            response = self._call_claude_via_ssh(prompt)
            if response:
                logger.debug(f"Claude CLI SSH 调用成功: {len(response)} 字符")
                return response
        except Exception as e:
            logger.debug(f"SSH 调用 Claude CLI 失败: {e}")

        # ========== 方式3: paramiko SSH ==========
        try:
            if not is_windows:
                result = subprocess.run(
                    ["claude", "--print"],
                    input=prompt,
                    capture_output=True,
                    text=True,
                    timeout=self.timeout,
                    env=env,
                    encoding="utf-8",
                    errors="replace",
                )
                if result.returncode == 0 and result.stdout and result.stdout.strip():
                    logger.debug(f"Claude CLI 容器内调用成功: {len(result.stdout)} 字符")
                    return result.stdout.strip()
        except Exception as e:
            logger.debug(f"容器内 Claude CLI 失败: {e}")

        raise Exception("Claude CLI 不可用（本地/SSH/容器均无法调用）")

    def _call_claude_via_ssh(self, prompt: str) -> str:
        """
        通过 SSH 调用宿主机的 Claude CLI

        Returns:
            str: Claude 响应文本

        Raises:
            Exception: SSH 调用失败时抛出
        """
        import tempfile

        host_ip = os.environ.get("CLAUDE_SSH_HOST", "172.17.0.1")
        host_port = int(os.environ.get("CLAUDE_SSH_PORT", "22"))
        host_user = os.environ.get("CLAUDE_SSH_USER", "ubains")
        host_pass = os.environ.get("CLAUDE_SSH_PASS", "Ubains@123")

        logger.info(f"通过 SSH 调用宿主机 Claude CLI: {host_user}@{host_ip}:{host_port}")

        with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False, encoding="utf-8") as f:
            f.write(prompt)
            prompt_file = f.name

        try:
            ssh_cmd = [
                "sshpass", "-p", host_pass,
                "ssh", "-o", "StrictHostKeyChecking=no",
                "-o", "ConnectTimeout=5",
                "-p", str(host_port),
                f"{host_user}@{host_ip}",
                "claude --print",
            ]

            result = subprocess.run(
                ssh_cmd,
                input=prompt,
                capture_output=True,
                text=True,
                timeout=self.timeout,
                encoding="utf-8",
                errors="replace",
            )

            if result.returncode == 0 and result.stdout and result.stdout.strip():
                return result.stdout.strip()

            if "sshpass" in (result.stderr or ""):
                logger.debug("sshpass 不可用，尝试 paramiko")
                return self._call_claude_via_paramiko(prompt, host_ip, host_port, host_user, host_pass)

            error_msg = result.stderr.strip() if result.stderr else "未知错误"
            raise Exception(f"SSH Claude CLI 错误: {error_msg[:200]}")

        except subprocess.TimeoutExpired:
            raise Exception(f"SSH Claude CLI 超时（>{self.timeout}s）")
        except FileNotFoundError:
            logger.debug("sshpass 未安装，尝试 paramiko")
            return self._call_claude_via_paramiko(prompt, host_ip, host_port, host_user, host_pass)
        finally:
            try:
                os.unlink(prompt_file)
            except Exception:
                pass

    def _call_claude_via_paramiko(self, prompt: str, host: str, port: int, user: str, password: str) -> str:
        """
        通过 paramiko SSH 调用 Claude CLI

        Returns:
            str: Claude 响应文本
        """
        try:
            import paramiko
        except ImportError:
            raise Exception("paramiko 未安装，无法通过 SSH 调用 Claude CLI")

        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

        try:
            ssh.connect(host, port=port, username=user, password=password, timeout=10)

            stdin, stdout, stderr = ssh.exec_command("claude --print", timeout=self.timeout)
            stdin.write(prompt)
            stdin.flush()
            stdin.channel.shutdown_write()

            out = stdout.read().decode("utf-8", errors="replace").strip()
            err = stderr.read().decode("utf-8", errors="replace").strip()

            if out:
                return out

            raise Exception(f"Paramiko Claude CLI 无输出: {err[:200]}")
        except Exception as e:
            raise Exception(f"Paramiko SSH 调用失败: {e}")
        finally:
            ssh.close()

    # ==================== 响应解析 ====================

    def _parse_json_response(self, response: str) -> dict:
        """
        解析 Claude 响应中的 JSON

        Args:
            response: Claude 原始响应文本

        Returns:
            dict: 解析后的 JSON 字典
        """
        # 尝试直接解析
        text = response.strip()

        # 移除 Markdown 代码块标记
        text = re.sub(r"^```(?:json)?\s*", "", text)
        text = re.sub(r"\s*```$", "", text)

        # 尝试 JSON 解析
        try:
            return json.loads(text)
        except json.JSONDecodeError:
            pass

        # 尝试提取第一个 { ... } 或 [ ... ]
        brace_match = re.search(r"\{.*\}", text, re.DOTALL)
        if brace_match:
            try:
                return json.loads(brace_match.group())
            except json.JSONDecodeError:
                pass

        bracket_match = re.search(r"\[.*\]", text, re.DOTALL)
        if bracket_match:
            try:
                result = json.loads(bracket_match.group())
                if isinstance(result, list) and len(result) > 0:
                    return result[0]
            except json.JSONDecodeError:
                pass

        raise ValueError(f"无法解析 Claude 响应为 JSON: {text[:200]}...")

    # ==================== 规则兜底分析 ====================

    def _rule_based_analysis(self, report_data: dict, analysis_type: str = "single") -> dict:
        """
        基于规则的兜底分析

        Args:
            report_data: 报告数据
            analysis_type: 分析类型

        Returns:
            dict: 分析结果
        """
        metrics = report_data.get("metrics", {})
        summary = report_data.get("summary", {})
        resource_summary = report_data.get("resource_summary") or {}
        target_resource_summary = report_data.get("target_resource_summary") or {}

        p95 = metrics.get("p95_response_time", 0)
        apdex = metrics.get("apdex", 1.0)
        error_rate = metrics.get("error_rate", 0)
        avg_tps = metrics.get("actual_tps", 0)
        peak_tps = metrics.get("peak_tps", 0)
        total_requests = metrics.get("total_requests", 0)
        status_4xx = metrics.get("status_4xx", 0)
        status_5xx = metrics.get("status_5xx", 0)

        # 总体评价
        verdict_parts = []
        if p95 < THRESHOLD_P95_GREEN:
            verdict_parts.append("✅ **响应时间优良**：P95 = {:.0f}ms，低于 300ms 阈值".format(p95))
        elif p95 < THRESHOLD_P95_YELLOW:
            verdict_parts.append("⚠️ **响应时间需关注**：P95 = {:.0f}ms，介于 300ms~1000ms 之间".format(p95))
        else:
            verdict_parts.append("❌ **响应时间瓶颈**：P95 = {:.0f}ms，超过 1000ms 阈值".format(p95))

        if apdex >= THRESHOLD_APDEX_GREEN:
            verdict_parts.append("✅ **Apdex 优良**：{:.2f}，高于 0.94 阈值".format(apdex))
        elif apdex >= THRESHOLD_APDEX_YELLOW:
            verdict_parts.append("⚠️ **Apdex 需关注**：{:.2f}，介于 0.85~0.94 之间".format(apdex))
        else:
            verdict_parts.append("❌ **Apdex 不达标**：{:.2f}，低于 0.85 阈值".format(apdex))

        if error_rate > THRESHOLD_ERROR_RATE:
            verdict_parts.append("❌ **错误率异常**：{:.2f}%，超过 5% 阈值".format(error_rate))
        else:
            verdict_parts.append("✅ **错误率正常**：{:.2f}%，低于 5% 阈值".format(error_rate))

        overall_verdict = "### 性能评估\n\n" + "\n\n".join(verdict_parts)

        # 关键发现
        key_findings = []
        if p95 >= THRESHOLD_P95_YELLOW:
            key_findings.append({
                "type": "异常",
                "description": "P95 响应时间 {:.0f}ms，超过 {}ms 阈值，用户侧感知延迟明显".format(p95, THRESHOLD_P95_YELLOW),
                "severity": "high" if p95 >= THRESHOLD_P95_YELLOW else "medium",
                "evidence": "P95 = {:.0f}ms（阈值 {}ms）".format(p95, THRESHOLD_P95_YELLOW),
            })
        if error_rate > THRESHOLD_ERROR_RATE:
            key_findings.append({
                "type": "异常",
                "description": "错误率 {:.2f}%，超过 {}% 阈值".format(error_rate, THRESHOLD_ERROR_RATE),
                "severity": "high",
                "evidence": "错误率 = {:.2f}%（阈值 {}%）".format(error_rate, THRESHOLD_ERROR_RATE),
            })
        if status_5xx > 0:
            key_findings.append({
                "type": "状态码",
                "description": "存在 {} 个 5xx 服务端错误".format(status_5xx),
                "severity": "high",
                "evidence": "status_5xx = {}".format(status_5xx),
            })
        if status_4xx > 0:
            key_findings.append({
                "type": "状态码",
                "description": "存在 {} 个 4xx 客户端错误".format(status_4xx),
                "severity": "medium",
                "evidence": "status_4xx = {}".format(status_4xx),
            })

        # 瓶颈分析
        bottlenecks = []
        if p95 >= THRESHOLD_P95_YELLOW:
            bottlenecks.append({
                "type": "响应时间",
                "location": summary.get("target_url", "服务器"),
                "severity": "high" if p95 >= THRESHOLD_P95_YELLOW else "medium",
                "description": "P95 响应时间 {:.0f}ms，存在性能瓶颈".format(p95),
                "evidence": "P95 = {:.0f}ms（阈值 {}ms）".format(p95, THRESHOLD_P95_YELLOW),
            })
        if error_rate > THRESHOLD_ERROR_RATE:
            bottlenecks.append({
                "type": "错误率",
                "location": "应用层",
                "severity": "high",
                "description": "错误率 {:.2f}% 超过阈值，存在请求失败问题".format(error_rate),
                "evidence": "错误率 = {:.2f}%（阈值 {}%）".format(error_rate, THRESHOLD_ERROR_RATE),
            })

        # 资源瓶颈
        cpu_usage = None
        mem_usage = None
        target_cpu = None
        target_mem = None

        if resource_summary:
            cpu_usage = resource_summary.get("cpu_avg") or resource_summary.get("cpu_average")
            mem_usage = resource_summary.get("memory_avg") or resource_summary.get("memory_average")
        if target_resource_summary:
            target_cpu = target_resource_summary.get("cpu_avg") or target_resource_summary.get("cpu_average")
            target_mem = target_resource_summary.get("memory_avg") or target_resource_summary.get("memory_average")

        if cpu_usage is not None and cpu_usage > THRESHOLD_CPU_HIGH:
            bottlenecks.append({
                "type": "资源",
                "location": "执行机 CPU",
                "severity": "high",
                "description": "执行机 CPU 平均使用率 {:.1f}%，超过 {}% 阈值".format(cpu_usage, THRESHOLD_CPU_HIGH),
                "evidence": "CPU 使用率 = {:.1f}%（阈值 {}%）".format(cpu_usage, THRESHOLD_CPU_HIGH),
            })
        if mem_usage is not None and mem_usage > THRESHOLD_MEMORY_HIGH:
            bottlenecks.append({
                "type": "资源",
                "location": "执行机内存",
                "severity": "high",
                "description": "执行机内存平均使用率 {:.1f}%，超过 {}% 阈值".format(mem_usage, THRESHOLD_MEMORY_HIGH),
                "evidence": "内存使用率 = {:.1f}%（阈值 {}%）".format(mem_usage, THRESHOLD_MEMORY_HIGH),
            })
        if target_cpu is not None and target_cpu > THRESHOLD_CPU_HIGH:
            bottlenecks.append({
                "type": "资源",
                "location": "目标机 CPU",
                "severity": "high",
                "description": "目标机 CPU 平均使用率 {:.1f}%，超过 {}% 阈值".format(target_cpu, THRESHOLD_CPU_HIGH),
                "evidence": "目标机 CPU 使用率 = {:.1f}%（阈值 {}%）".format(target_cpu, THRESHOLD_CPU_HIGH),
            })
        if target_mem is not None and target_mem > THRESHOLD_MEMORY_HIGH:
            bottlenecks.append({
                "type": "资源",
                "location": "目标机内存",
                "severity": "high",
                "description": "目标机内存平均使用率 {:.1f}%，超过 {}% 阈值".format(target_mem, THRESHOLD_MEMORY_HIGH),
                "evidence": "目标机内存使用率 = {:.1f}%（阈值 {}%）".format(target_mem, THRESHOLD_MEMORY_HIGH),
            })

        # 优化建议
        suggestions = []
        if p95 >= THRESHOLD_P95_YELLOW:
            suggestions.append({
                "priority": "P0" if p95 >= THRESHOLD_P95_YELLOW else "P1",
                "content": "优化接口响应时间：P95 当前 {:.0f}ms，建议检查数据库查询、缓存策略或代码逻辑".format(p95),
                "expectedEffect": "预期可将 P95 降至 {}ms 以下".format(THRESHOLD_P95_YELLOW),
            })
        if error_rate > THRESHOLD_ERROR_RATE:
            suggestions.append({
                "priority": "P0",
                "content": "排查错误请求：错误率 {:.2f}%，建议检查 4xx/5xx 具体错误类型并修复".format(error_rate),
                "expectedEffect": "预期可将错误率降至 5% 以下",
            })
        if cpu_usage is not None and cpu_usage > THRESHOLD_CPU_HIGH:
            suggestions.append({
                "priority": "P1",
                "content": "执行机 CPU 过高（{:.1f}%），建议增加并发线程数控制或升级执行机配置".format(cpu_usage),
                "expectedEffect": "降低 CPU 负载，避免压测工具自身成为瓶颈",
            })
        if target_cpu is not None and target_cpu > THRESHOLD_CPU_HIGH:
            suggestions.append({
                "priority": "P1",
                "content": "目标机 CPU 过高（{:.1f}%），建议扩容或优化服务端处理能力".format(target_cpu),
                "expectedEffect": "提升服务端吞吐能力，降低响应时间",
            })
        if not suggestions:
            suggestions.append({
                "priority": "P2",
                "content": "当前性能表现良好，建议持续监控并在更高并发下验证",
                "expectedEffect": "确保系统在更大压力下仍保持稳定",
            })

        # 资源分析
        resource_parts = []
        if cpu_usage is not None:
            resource_parts.append("**执行机 CPU**：平均使用率 {:.1f}%{}".format(
                cpu_usage, " ⚠️ 超过阈值" if cpu_usage > THRESHOLD_CPU_HIGH else ""
            ))
        if mem_usage is not None:
            resource_parts.append("**执行机内存**：平均使用率 {:.1f}%{}".format(
                mem_usage, " ⚠️ 超过阈值" if mem_usage > THRESHOLD_MEMORY_HIGH else ""
            ))
        if target_cpu is not None:
            resource_parts.append("**目标机 CPU**：平均使用率 {:.1f}%{}".format(
                target_cpu, " ⚠️ 超过阈值" if target_cpu > THRESHOLD_CPU_HIGH else ""
            ))
        if target_mem is not None:
            resource_parts.append("**目标机内存**：平均使用率 {:.1f}%{}".format(
                target_mem, " ⚠️ 超过阈值" if target_mem > THRESHOLD_MEMORY_HIGH else ""
            ))
        if not resource_parts:
            resource_parts.append("无资源监控数据，建议开启目标机/执行机资源监控以获得更全面的分析")

        resource_analysis = "### 资源使用分析\n\n" + "\n\n".join(resource_parts)

        return {
            "overall_verdict": overall_verdict,
            "key_findings": key_findings,
            "bottlenecks": bottlenecks,
            "suggestions": suggestions,
            "resource_analysis": resource_analysis,
            "source": "rule",
        }

    def _rule_based_comparison(self, report_data_list: list) -> dict:
        """
        基于规则的对比分析兜底

        Args:
            report_data_list: 报告数据列表

        Returns:
            dict: 对比分析结果
        """
        if len(report_data_list) < 2:
            return self._rule_based_analysis(report_data_list[0] if report_data_list else {}, analysis_type="single")

        first = report_data_list[0].get("metrics", {})
        last = report_data_list[-1].get("metrics", {})

        comparison = []
        key_metrics = [
            ("actual_tps", "TPS", True),
            ("peak_tps", "峰值 TPS", True),
            ("avg_response_time", "平均响应时间", False),
            ("p95_response_time", "P95 响应时间", False),
            ("p99_response_time", "P99 响应时间", False),
            ("error_rate", "错误率", False),
            ("apdex", "Apdex", True),
            ("total_requests", "总请求数", True),
            ("std_dev", "响应时间标准差", False),
        ]

        for key, label, higher_is_better in key_metrics:
            base = first.get(key)
            current = last.get(key)
            if base is None or current is None or base == 0:
                continue

            change_pct = round((current - base) / base * 100, 1)
            if higher_is_better:
                direction = "improved" if current > base else ("regressed" if current < base else "stable")
            else:
                direction = "improved" if current < base else ("regressed" if current > base else "stable")

            comparison.append({
                "metric": label,
                "base_value": base,
                "current_value": current,
                "change_percent": change_pct,
                "direction": direction,
                "assessment": self._assess_change(label, change_pct, direction),
            })

        # 总体评价
        verdict_parts = ["### 对比分析评估\n"]
        improved_count = sum(1 for c in comparison if c["direction"] == "improved")
        regressed_count = sum(1 for c in comparison if c["direction"] == "regressed")

        if improved_count > regressed_count:
            verdict_parts.append("✅ **整体性能趋势向好**：{} 项指标改善，{} 项指标退化".format(improved_count, regressed_count))
        elif regressed_count > improved_count:
            verdict_parts.append("❌ **整体性能趋势下降**：{} 项指标改善，{} 项指标退化".format(improved_count, regressed_count))
        else:
            verdict_parts.append("➡️ **整体性能基本稳定**：{} 项改善，{} 项退化，{} 项持平".format(
                improved_count, regressed_count,
                sum(1 for c in comparison if c["direction"] == "stable"),
            ))

        # 关键发现
        key_findings = []
        for c in comparison:
            if c["direction"] == "regressed" and abs(c["change_percent"]) > 10:
                key_findings.append({
                    "type": "回归",
                    "description": "{} 退化 {:.1f}%（从 {} 到 {}）".format(
                        c["metric"], c["change_percent"], c["base_value"], c["current_value"]
                    ),
                    "severity": "high" if abs(c["change_percent"]) > 20 else "medium",
                    "evidence": "{}: {:.1f}% 变化".format(c["metric"], c["change_percent"]),
                })
            elif c["direction"] == "improved" and abs(c["change_percent"]) > 20:
                key_findings.append({
                    "type": "改善",
                    "description": "{} 改善 {:.1f}%（从 {} 到 {}）".format(
                        c["metric"], c["change_percent"], c["base_value"], c["current_value"]
                    ),
                    "severity": "low",
                    "evidence": "{}: +{:.1f}%".format(c["metric"], c["change_percent"]),
                })

        return {
            "overall_verdict": "\n\n".join(verdict_parts),
            "key_findings": key_findings,
            "bottlenecks": [],
            "suggestions": [],
            "comparison": comparison,
            "source": "rule",
        }

    @staticmethod
    def _assess_change(metric: str, change_pct: float, direction: str) -> str:
        """生成指标变化的评估描述"""
        if direction == "stable":
            return "{} 基本保持不变（{:.1f}%）".format(metric, change_pct)
        if direction == "improved" and change_pct > 0:
            return "{} 提升 {:.1f}%，表现改善".format(metric, abs(change_pct))
        if direction == "regressed":
            return "{} 下降 {:.1f}%，需关注".format(metric, abs(change_pct))
        return "{} 变化 {:.1f}%".format(metric, change_pct)