# -*- coding: utf-8 -*-
"""
compare_service.py — 报告对比服务

功能：
  - 对比两份巡检报告，分类差异项
  - 导出对比结果（Markdown/HTML）
"""

from __future__ import annotations

import logging
import re
from typing import Dict, List, Optional, Tuple

from .report_service import get_report, NORMAL, WARNING, CRITICAL

logger = logging.getLogger("service_monitor.compare_service")

# 值变化阈值（变化率 > 20% 视为"值变化"）
VALUE_CHANGE_THRESHOLD = 0.2


def compare_reports(report_id_a: str, report_id_b: str) -> Optional[Dict]:
    """对比两份报告，返回差异结果。

    Args:
        report_id_a: 基准报告 ID
        report_id_b: 对比报告 ID

    Returns:
        {
            "report_a": {...},
            "report_b": {...},
            "diff_summary": {"new_abnormal": 5, "recovered": 3, ...},
            "diff_details": {"new_abnormal": [...], ...}
        }
        None: 任一报告不存在
        {"error": "targets_mismatch"}: 目标不匹配
    """
    report_a = get_report(report_id_a)
    report_b = get_report(report_id_b)

    if not report_a or not report_b:
        return None

    # 验证同一目标
    if report_a.get("target_id") != report_b.get("target_id"):
        return {"error": "targets_mismatch"}

    # 构建检测项索引（key -> item）
    items_a = _build_item_index(report_a)
    items_b = _build_item_index(report_b)

    # 对比
    new_abnormal = []
    recovered = []
    value_changed = []
    new_items = []
    removed_items = []

    # 遍历报告 B（对比报告）
    for key, item_b in items_b.items():
        if key not in items_a:
            new_items.append(_build_diff_item(key, None, item_b, "new_item"))
            continue

        item_a = items_a[key]
        diff_type = _classify_diff(item_a, item_b)

        if diff_type == "new_abnormal":
            new_abnormal.append(_build_diff_item(key, item_a, item_b, diff_type))
        elif diff_type == "recovered":
            recovered.append(_build_diff_item(key, item_a, item_b, diff_type))
        elif diff_type == "value_changed":
            value_changed.append(_build_diff_item(key, item_a, item_b, diff_type))

    # 遍历报告 A，找出消失的检测项
    for key, item_a in items_a.items():
        if key not in items_b:
            removed_items.append(_build_diff_item(key, item_a, None, "removed_item"))

    # 按模块分组
    diff_details = {
        "new_abnormal": _group_by_module(new_abnormal),
        "recovered": _group_by_module(recovered),
        "value_changed": _group_by_module(value_changed),
        "new_items": _group_by_module(new_items),
        "removed_items": _group_by_module(removed_items),
    }

    diff_summary = {
        "new_abnormal": len(new_abnormal),
        "recovered": len(recovered),
        "value_changed": len(value_changed),
        "new_items": len(new_items),
        "removed_items": len(removed_items),
    }

    return {
        "report_a": _report_summary(report_a),
        "report_b": _report_summary(report_b),
        "diff_summary": diff_summary,
        "diff_details": diff_details,
    }


def export_compare_markdown(data: Dict) -> str:
    """导出对比结果为 Markdown。"""
    ra = data.get("report_a", {})
    rb = data.get("report_b", {})
    summary = data.get("diff_summary", {})
    details = data.get("diff_details", {})

    lines = [
        "# 巡检报告对比",
        "",
        "| | 基准报告 | 对比报告 |",
        "|---|---------|---------|",
        f"| 报告ID | {ra.get('id', '-')} | {rb.get('id', '-')} |",
        f"| 目标 | {ra.get('target_name', '-')} | {rb.get('target_name', '-')} |",
        f"| 时间 | {ra.get('created_at', '-')} | {rb.get('created_at', '-')} |",
        f"| 总项数 | {ra.get('total_items', '-')} | {rb.get('total_items', '-')} |",
        f"| 正常 | {ra.get('normal_count', '-')} | {rb.get('normal_count', '-')} |",
        f"| 警告 | {ra.get('warning_count', '-')} | {rb.get('warning_count', '-')} |",
        f"| 严重 | {ra.get('critical_count', '-')} | {rb.get('critical_count', '-')} |",
        "",
        "## 差异汇总",
        "",
        "| 类型 | 数量 |",
        "|------|------|",
        f"| 新增异常 | {summary.get('new_abnormal', 0)} |",
        f"| 恢复正常 | {summary.get('recovered', 0)} |",
        f"| 值变化 | {summary.get('value_changed', 0)} |",
        f"| 新增检测项 | {summary.get('new_items', 0)} |",
        f"| 消失检测项 | {summary.get('removed_items', 0)} |",
        "",
    ]

    # 各类差异明细
    type_labels = {
        "new_abnormal": ("新增异常", "warning"),
        "recovered": ("恢复正常", "recovered"),
        "value_changed": ("值变化", "changed"),
        "new_items": ("新增检测项", "new"),
        "removed_items": ("消失检测项", "removed"),
    }

    for diff_type, (label, _) in type_labels.items():
        items = details.get(diff_type, [])
        if not items:
            continue
        lines += [f"## {label}", ""]
        for group in items:
            lines += [f"### {group.get('module_name', '未知模块')}", ""]
            lines += ["| 检测项 | 基准值 | 对比值 | 变化 |",
                       "|--------|--------|--------|------|"]
            for it in group.get("items", []):
                change_desc = it.get("change_desc", "-")
                lines.append(
                    f"| {it.get('name', it.get('key', '-'))} "
                    f"| {it.get('value_a', '-')} "
                    f"| {it.get('value_b', '-')} "
                    f"| {change_desc} |"
                )
            lines.append("")

    return "\n".join(lines)


def export_compare_html(data: Dict) -> str:
    """导出对比结果为 HTML（内联样式，适合邮件/打印）。"""
    ra = data.get("report_a", {})
    rb = data.get("report_b", {})
    summary = data.get("diff_summary", {})
    details = data.get("diff_details", {})

    html_parts = [
        "<!DOCTYPE html><html><head><meta charset='utf-8'>",
        "<title>巡检报告对比</title>",
        "<style>",
        "body{font-family:'Microsoft YaHei',sans-serif;max-width:960px;margin:0 auto;padding:20px;color:#333}",
        "h1{color:#1a73e8;border-bottom:2px solid #1a73e8;padding-bottom:8px}",
        "h2{color:#333;margin-top:24px}",
        "h3{color:#555}",
        "table{border-collapse:collapse;width:100%;margin:8px 0}",
        "th,td{border:1px solid #ddd;padding:8px 12px;text-align:left}",
        "th{background:#f5f5f5;font-weight:600}",
        ".summary-cards{display:flex;gap:12px;margin:16px 0}",
        ".card{flex:1;padding:12px;border-radius:8px;text-align:center}",
        ".card-abnormal{background:#fff0f0;border:1px solid #ffccc7}",
        ".card-recovered{background:#f0fff0;border:1px solid #b7eb8f}",
        ".card-changed{background:#fffbe6;border:1px solid #ffe58f}",
        ".card-new{background:#e6f7ff;border:1px solid #91d5ff}",
        ".card-removed{background:#f5f5f5;border:1px solid #d9d9d9}",
        ".card .count{font-size:28px;font-weight:700}",
        ".card .label{font-size:13px;color:#666}",
        ".tag-abnormal{color:#cf1322;font-weight:600}",
        ".tag-recovered{color:#389e0d;font-weight:600}",
        ".tag-changed{color:#d48806;font-weight:600}",
        ".tag-new{color:#096dd9;font-weight:600}",
        ".tag-removed{color:#8c8c8c}",
        "</style></head><body>",
        "<h1>巡检报告对比</h1>",
        _html_report_table(ra, rb),
        _html_summary_cards(summary),
    ]

    type_labels = {
        "new_abnormal": ("新增异常", "abnormal"),
        "recovered": ("恢复正常", "recovered"),
        "value_changed": ("值变化", "changed"),
        "new_items": ("新增检测项", "new"),
        "removed_items": ("消失检测项", "removed"),
    }

    for diff_type, (label, css_cls) in type_labels.items():
        items = details.get(diff_type, [])
        if not items:
            continue
        html_parts.append(f"<h2>{label}</h2>")
        for group in items:
            html_parts.append(f"<h3>{group.get('module_name', '未知模块')}</h3>")
            html_parts.append("<table><tr><th>检测项</th><th>基准值</th><th>对比值</th><th>变化</th></tr>")
            for it in group.get("items", []):
                change_desc = it.get("change_desc", "-")
                html_parts.append(
                    f"<tr>"
                    f"<td class='tag-{css_cls}'>{it.get('name', it.get('key', '-'))}</td>"
                    f"<td>{it.get('value_a', '-')}</td>"
                    f"<td>{it.get('value_b', '-')}</td>"
                    f"<td>{change_desc}</td>"
                    f"</tr>"
                )
            html_parts.append("</table>")

    html_parts.append("</body></html>")
    return "\n".join(html_parts)


# ============================================================
# 内部辅助函数
# ============================================================

def _build_item_index(report: Dict) -> Dict[str, Dict]:
    """构建检测项索引：key -> {key, name, value, status, module_id, module_name}"""
    index = {}
    for m in report.get("modules", []):
        module_id = m.get("id", "")
        module_name = m.get("name", "")
        for it in m.get("items", []):
            key = it.get("key", "")
            if key:
                index[key] = {
                    "key": key,
                    "name": it.get("name", key),
                    "value": it.get("value", ""),
                    "status": it.get("status", NORMAL),
                    "module_id": module_id,
                    "module_name": module_name,
                }
    return index


def _classify_diff(item_a: Dict, item_b: Dict) -> str:
    """分类单个检测项的差异类型。

    Returns:
        "new_abnormal" | "recovered" | "value_changed" | "same"
    """
    status_a = item_a.get("status", NORMAL)
    status_b = item_b.get("status", NORMAL)

    is_abnormal_a = status_a in (WARNING, CRITICAL)
    is_abnormal_b = status_b in (WARNING, CRITICAL)

    # 状态变化
    if not is_abnormal_a and is_abnormal_b:
        return "new_abnormal"
    if is_abnormal_a and not is_abnormal_b:
        return "recovered"

    # 值变化（仅数值型，状态相同时）
    if status_a == status_b:
        change_rate = _calculate_value_change(item_a.get("value", ""), item_b.get("value", ""))
        if change_rate >= 0 and change_rate > VALUE_CHANGE_THRESHOLD:
            return "value_changed"

    return "same"


def _calculate_value_change(value_a: str, value_b: str) -> float:
    """计算数值变化率。

    Returns:
        变化率（0.0~1.0+），非数值返回 -1
    """
    num_a = _extract_numeric(value_a)
    num_b = _extract_numeric(value_b)

    if num_a is None or num_b is None:
        return -1.0

    if num_a == 0 and num_b == 0:
        return 0.0
    if num_a == 0:
        return 1.0  # 0 → 非零，视为 100% 变化

    return abs(num_b - num_a) / abs(num_a)


_NUM_RE = re.compile(r"[-+]?\d*\.?\d+")


def _extract_numeric(value: str) -> Optional[float]:
    """从值字符串中提取数值。"""
    if not value:
        return None
    m = _NUM_RE.search(str(value))
    if m:
        try:
            return float(m.group())
        except (ValueError, TypeError):
            return None
    return None


def _build_diff_item(key: str, item_a: Optional[Dict], item_b: Optional[Dict],
                     diff_type: str) -> Dict:
    """构建差异项。"""
    result = {
        "key": key,
        "name": (item_b or item_a or {}).get("name", key),
        "module_id": (item_b or item_a or {}).get("module_id", ""),
        "module_name": (item_b or item_a or {}).get("module_name", ""),
        "diff_type": diff_type,
        "value_a": item_a.get("value", "-") if item_a else "-",
        "value_b": item_b.get("value", "-") if item_b else "-",
        "status_a": item_a.get("status", "-") if item_a else "-",
        "status_b": item_b.get("status", "-") if item_b else "-",
    }

    # 变化说明
    result["change_desc"] = _describe_change(result)

    return result


def _describe_change(item: Dict) -> str:
    """生成变化说明文字。"""
    diff_type = item.get("diff_type", "")
    if diff_type == "new_abnormal":
        return f"{item.get('status_a', '-')} -> {item.get('status_b', '-')}"
    if diff_type == "recovered":
        return f"{item.get('status_a', '-')} -> {item.get('status_b', '-')}"
    if diff_type == "value_changed":
        val_a = item.get("value_a", "-")
        val_b = item.get("value_b", "-")
        num_a = _extract_numeric(str(val_a))
        num_b = _extract_numeric(str(val_b))
        if num_a is not None and num_b is not None and num_a != 0:
            pct = ((num_b - num_a) / abs(num_a)) * 100
            sign = "+" if pct > 0 else ""
            return f"{val_a} -> {val_b} ({sign}{pct:.1f}%)"
        return f"{val_a} -> {val_b}"
    if diff_type == "new_item":
        return "新增检测项"
    if diff_type == "removed_item":
        return "消失检测项"
    return "-"


def _group_by_module(items: List[Dict]) -> List[Dict]:
    """按模块分组差异项。"""
    groups = {}
    for it in items:
        mod = it.get("module_id", "unknown")
        if mod not in groups:
            groups[mod] = {
                "module_id": mod,
                "module_name": it.get("module_name", "未知模块"),
                "items": [],
            }
        groups[mod]["items"].append(it)
    return list(groups.values())


def _report_summary(report: Dict) -> Dict:
    """提取报告摘要信息。"""
    s = report.get("summary", {})
    return {
        "id": report.get("id", ""),
        "target_id": report.get("target_id", ""),
        "target_name": report.get("target_name", ""),
        "target_type": report.get("target_type", ""),
        "suite": report.get("suite", ""),
        "created_at": report.get("finished_at") or report.get("started_at", ""),
        "total_items": s.get("total", 0),
        "normal_count": s.get(NORMAL, 0),
        "warning_count": s.get(WARNING, 0),
        "critical_count": s.get(CRITICAL, 0),
    }


def _html_report_table(ra: Dict, rb: Dict) -> str:
    """生成报告信息对比 HTML 表格。"""
    return (
        "<table>"
        "<tr><th></th><th>基准报告</th><th>对比报告</th></tr>"
        f"<tr><td>报告ID</td><td>{ra.get('id','-')}</td><td>{rb.get('id','-')}</td></tr>"
        f"<tr><td>目标</td><td>{ra.get('target_name','-')}</td><td>{rb.get('target_name','-')}</td></tr>"
        f"<tr><td>时间</td><td>{ra.get('created_at','-')}</td><td>{rb.get('created_at','-')}</td></tr>"
        f"<tr><td>总项数</td><td>{ra.get('total_items','-')}</td><td>{rb.get('total_items','-')}</td></tr>"
        f"<tr><td>正常</td><td>{ra.get('normal_count','-')}</td><td>{rb.get('normal_count','-')}</td></tr>"
        f"<tr><td>警告</td><td>{ra.get('warning_count','-')}</td><td>{rb.get('warning_count','-')}</td></tr>"
        f"<tr><td>严重</td><td>{ra.get('critical_count','-')}</td><td>{rb.get('critical_count','-')}</td></tr>"
        "</table>"
    )


def _html_summary_cards(summary: Dict) -> str:
    """生成差异汇总 HTML 卡片。"""
    cards = [
        ("card-abnormal", "新增异常", summary.get("new_abnormal", 0)),
        ("card-recovered", "恢复正常", summary.get("recovered", 0)),
        ("card-changed", "值变化", summary.get("value_changed", 0)),
        ("card-new", "新增检测项", summary.get("new_items", 0)),
        ("card-removed", "消失检测项", summary.get("removed_items", 0)),
    ]
    parts = ['<div class="summary-cards">']
    for css_cls, label, count in cards:
        parts.append(
            f'<div class="card {css_cls}">'
            f'<div class="count">{count}</div>'
            f'<div class="label">{label}</div>'
            f'</div>'
        )
    parts.append("</div>")
    return "\n".join(parts)
