"""把 AuditResult 渲染成单文件 HTML 报告。"""

from __future__ import annotations

from collections import Counter
from html import escape

from fastapi_modules.fastapi_leaudit.govdoc_engine.engine.result import AuditResult


_CSS = """
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
  background: #f3f6f5;
  color: #0f172a;
}
a { color: inherit; }
.page {
  width: 100%;
  padding: 20px 24px 32px;
}
.stack {
  display: flex;
  flex-direction: column;
  gap: 20px;
}
.card {
  background: #ffffff;
  border: 1px solid #e2e8f0;
  border-radius: 12px;
  box-shadow: 0 1px 3px rgba(15, 23, 42, 0.08);
  overflow: hidden;
}
.card-head {
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 0 20px;
  border-bottom: 1px solid #e2e8f0;
  background: #fcfdfd;
}
.card-title {
  font-size: 14px;
  font-weight: 600;
  color: #1e293b;
}
.card-subtitle {
  font-size: 12px;
  color: #64748b;
}
.summary-grid {
  display: grid;
  grid-template-columns: 220px minmax(0, 1fr);
  gap: 20px;
  padding: 20px;
}
.score-box {
  border: 1px solid #cfe4dc;
  background: #f7fbf9;
  border-radius: 10px;
  padding: 20px;
}
.score-label {
  font-size: 12px;
  font-weight: 500;
  color: #475569;
}
.score-value {
  margin-top: 12px;
  font-size: 42px;
  line-height: 1;
  font-weight: 600;
  letter-spacing: -0.05em;
  color: #0f172a;
}
.score-track {
  margin-top: 16px;
  height: 8px;
  background: #dbe8e3;
  border-radius: 999px;
  overflow: hidden;
}
.score-fill {
  height: 100%;
  background: #00684a;
}
.score-note {
  margin-top: 16px;
  font-size: 12px;
  line-height: 1.75;
  color: #475569;
}
.summary-main {
  min-width: 0;
}
.eyebrow {
  display: inline-flex;
  align-items: center;
  height: 28px;
  padding: 0 12px;
  border: 1px solid #cfe4dc;
  border-radius: 6px;
  background: #e8f3ef;
  color: #00684a;
  font-size: 12px;
  font-weight: 500;
}
.report-title {
  margin: 12px 0 0;
  font-size: 32px;
  line-height: 1.25;
  letter-spacing: -0.03em;
  font-weight: 600;
  color: #0f172a;
}
.report-meta {
  margin-top: 8px;
  font-size: 15px;
  color: #475569;
}
.metrics {
  margin-top: 20px;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 16px;
}
.metric {
  border: 1px solid #e2e8f0;
  border-radius: 10px;
  background: #fcfdfd;
  padding: 16px 20px;
}
.metric-label {
  font-size: 13px;
  font-weight: 500;
  color: #64748b;
}
.metric-value {
  margin-top: 12px;
  display: flex;
  align-items: baseline;
  gap: 8px;
}
.metric-value strong {
  font-size: 30px;
  line-height: 1;
  letter-spacing: -0.04em;
  font-weight: 600;
  color: #0f172a;
}
.metric-value span {
  font-size: 13px;
  color: #64748b;
}
.chips {
  margin-top: 20px;
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}
.chip,
.severity-tag {
  display: inline-flex;
  align-items: center;
  border: 1px solid transparent;
  border-radius: 6px;
  font-weight: 600;
}
.chip {
  height: 32px;
  padding: 0 12px;
  font-size: 12px;
}
.severity-tag {
  height: 32px;
  padding: 0 12px;
  font-size: 12px;
  text-transform: uppercase;
}
.error {
  border-color: #fecaca;
  background: #fef2f2;
  color: #b91c1c;
}
.warning {
  border-color: #fde68a;
  background: #fffbeb;
  color: #b45309;
}
.info {
  border-color: #bfdbfe;
  background: #eff6ff;
  color: #1d4ed8;
}
.content-grid {
  display: grid;
  grid-template-columns: 340px minmax(0, 1fr);
  gap: 20px;
}
.sidebar-body {
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.summary-row {
  border: 1px solid #e2e8f0;
  border-radius: 10px;
  background: #fcfdfd;
  padding: 16px;
}
.summary-row-label {
  font-size: 12px;
  font-weight: 500;
  color: #64748b;
}
.summary-row-value {
  margin-top: 8px;
  font-size: 22px;
  line-height: 1;
  letter-spacing: -0.03em;
  font-weight: 600;
  color: #0f172a;
}
.summary-row-desc {
  margin-top: 12px;
  font-size: 13px;
  line-height: 1.75;
  color: #475569;
}
.table-toolbar {
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 0 20px;
  border-bottom: 1px solid #e2e8f0;
  background: #fcfdfd;
}
.toolbar-left {
  min-width: 0;
}
.toolbar-title {
  font-size: 14px;
  font-weight: 600;
  color: #1e293b;
}
.toolbar-desc {
  margin-top: 2px;
  font-size: 12px;
  color: #64748b;
}
.toolbar-filters {
  display: flex;
  gap: 8px;
}
.filter {
  display: inline-flex;
  align-items: center;
  height: 32px;
  padding: 0 12px;
  border: 1px solid #e2e8f0;
  border-radius: 6px;
  background: #ffffff;
  color: #64748b;
  font-size: 12px;
  font-weight: 500;
}
.filter.active {
  border-color: rgba(0, 104, 74, 0.2);
  background: #e8f3ef;
  color: #00684a;
}
.table-wrap {
  overflow-x: auto;
}
table {
  width: 100%;
  min-width: 1320px;
  border-collapse: collapse;
}
thead tr {
  background: #f8fafc;
  color: #475569;
  font-size: 13px;
  font-weight: 500;
}
th {
  padding: 16px 20px;
  text-align: left;
  border-bottom: 1px solid #e2e8f0;
  white-space: nowrap;
}
td {
  padding: 20px;
  vertical-align: top;
  border-bottom: 1px solid #f1f5f9;
}
tbody tr:hover {
  background: #f8fafc;
}
.mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.id-cell {
  font-size: 13px;
  color: #64748b;
}
.rule-id {
  font-size: 15px;
  font-weight: 600;
  color: #1e293b;
}
.rule-name {
  margin-top: 4px;
  font-size: 13px;
  color: #64748b;
}
.category-cell {
  font-size: 14px;
  color: #334155;
}
.location-cell {
  font-size: 13px;
  color: #334155;
}
.message-cell {
  min-width: 560px;
}
.message-main {
  font-size: 15px;
  line-height: 1.8;
  color: #0f172a;
}
.context-box,
.suggestion-box {
  margin-top: 12px;
  border-radius: 6px;
  padding: 12px 16px;
  font-size: 13px;
  line-height: 1.8;
}
.context-box {
  border: 1px solid #e2e8f0;
  background: #f8fafc;
  color: #475569;
}
.suggestion-box {
  border: 1px solid #cfe4dc;
  background: #f4faf7;
  color: #0d6b4d;
}
.empty {
  padding: 24px 20px;
  text-align: center;
  color: #64748b;
  font-size: 14px;
}
@media (max-width: 1200px) {
  .summary-grid,
  .content-grid {
    grid-template-columns: 1fr;
  }
  .metrics {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (max-width: 720px) {
  .page {
    padding: 16px;
  }
  .metrics {
    grid-template-columns: 1fr;
  }
  .table-toolbar,
  .card-head {
    height: auto;
    min-height: 48px;
    padding-top: 12px;
    padding-bottom: 12px;
    align-items: flex-start;
    flex-direction: column;
  }
}
"""


def _summary_score(summary: object) -> int:
    for attr in ("score", "totalScore", "total_score"):
        value = getattr(summary, attr, None)
        if value is None and isinstance(summary, dict):
            value = summary.get(attr)
        if value is None:
            continue
        try:
            return int(float(value))
        except (TypeError, ValueError):
            continue
    return 0


def _severity_label(severity: str) -> str:
    return {
        "error": "错误",
        "warning": "警告",
        "info": "提示",
    }.get(severity, severity)


def render_html(result: AuditResult) -> str:
    summary = result.summary
    score = _summary_score(summary)
    score_pct = max(0, min(score, 100))
    severity_counts = _severity_counts(result)
    category_count = len([key for key, value in (summary.by_category or {}).items() if key and value])
    filename = escape(str(result.document.get("filename", "")))
    top_rule_id, top_rule_count = _top_rule(result)
    line_range = _line_range(result)
    entity_summary = _entity_summary(result)

    rows = []
    for finding in result.findings:
        location_label = _format_location(finding.location.paragraph_index)
        context = escape((finding.location.context or "").strip())
        message = escape(finding.message)
        suggestion = escape(finding.suggestion) if finding.suggestion else "按规则要求修正对应内容。"
        severity = escape(finding.severity)
        severity_label = escape(_severity_label(finding.severity))

        rows.append(
            f"""
<tr>
  <td class="mono id-cell">{escape(finding.finding_id)}</td>
  <td>
    <div class="rule-id">{escape(finding.rule_id)}</div>
    <div class="rule-name">{escape(finding.rule_name)}</div>
  </td>
  <td><span class="severity-tag {severity}">{severity_label}</span></td>
  <td class="category-cell">{escape(finding.category)}</td>
  <td class="mono location-cell">{location_label}</td>
  <td class="message-cell">
    <div class="message-main">{message}</div>
    <div class="context-box">原文：{context or "未提取到上下文"}</div>
    <div class="suggestion-box">建议：{suggestion}</div>
  </td>
</tr>"""
        )

    return f"""<!doctype html>
<html lang="zh">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>公文审核报告</title>
  <style>{_CSS}</style>
</head>
<body>
  <div class="page">
    <div class="stack">
      <section class="card">
        <div class="card-head">
          <div class="card-title">报告摘要</div>
        </div>
        <div class="summary-grid">
          <div class="score-box">
            <div class="score-label">综合得分</div>
            <div class="score-value">{score}</div>
            <div class="score-track"><div class="score-fill" style="width:{score_pct}%"></div></div>
            <div class="score-note">综合反映本次公文审核结果，可结合下方问题明细逐项核对修正。</div>
          </div>

          <div class="summary-main">
            <div class="eyebrow">审核结果</div>
            <h1 class="report-title">公文格式审核报告</h1>
            <div class="report-meta">{filename} · 共 {summary.total_findings} 项问题</div>

            <div class="metrics">
              <div class="metric">
                <div class="metric-label">错误项</div>
                <div class="metric-value"><strong>{severity_counts["error"]}</strong><span>错误</span></div>
              </div>
              <div class="metric">
                <div class="metric-label">警告项</div>
                <div class="metric-value"><strong>{severity_counts["warning"]}</strong><span>警告</span></div>
              </div>
              <div class="metric">
                <div class="metric-label">提示项</div>
                <div class="metric-value"><strong>{severity_counts["info"]}</strong><span>提示</span></div>
              </div>
              <div class="metric">
                <div class="metric-label">问题类别</div>
                <div class="metric-value"><strong>{category_count}</strong><span>标题 / 发文 / 格式 / 其他</span></div>
              </div>
            </div>

            <div class="chips">
              <span class="chip error">错误 {severity_counts["error"]}</span>
              <span class="chip warning">警告 {severity_counts["warning"]}</span>
              <span class="chip info">提示 {severity_counts["info"]}</span>
            </div>
          </div>
        </div>
      </section>

      <section class="content-grid">
        <aside class="card">
          <div class="card-head">
            <div class="card-title">侧边摘要</div>
            <div class="card-subtitle">关键信息</div>
          </div>
          <div class="sidebar-body">
            <article class="summary-row">
              <div class="summary-row-label">命中最多规则</div>
              <div class="summary-row-value">{escape(top_rule_id)}</div>
              <div class="summary-row-desc">当前命中最多的规则共 {top_rule_count} 项。</div>
            </article>
            <article class="summary-row">
              <div class="summary-row-label">集中行号</div>
              <div class="summary-row-value">{escape(line_range)}</div>
              <div class="summary-row-desc">问题主要集中在以上行号范围。</div>
            </article>
            <article class="summary-row">
              <div class="summary-row-label">实体状态</div>
              <div class="summary-row-value">{escape(entity_summary)}</div>
              <div class="summary-row-desc">按当前识别结果汇总核心实体状态。</div>
            </article>
          </div>
        </aside>

        <article class="card">
          <div class="table-toolbar">
            <div class="toolbar-left">
              <div class="toolbar-title">问题明细</div>
            </div>
            <div class="toolbar-filters">
              <span class="filter active">全部</span>
              <span class="filter">错误</span>
              <span class="filter">警告</span>
            </div>
          </div>
          <div class="table-wrap">
            <table>
              <thead>
                <tr>
                  <th>编号</th>
                  <th>规则</th>
                  <th>严重度</th>
                  <th>类别</th>
                  <th>位置</th>
                  <th>说明</th>
                </tr>
              </thead>
              <tbody>
                {''.join(rows) or '<tr><td colspan="6" class="empty">未发现问题</td></tr>'}
              </tbody>
            </table>
          </div>
        </article>
      </section>
    </div>
  </div>
</body>
</html>"""


def _severity_counts(result: AuditResult) -> dict[str, int]:
    counts = Counter(finding.severity for finding in result.findings)
    return {
      "error": counts.get("error", 0),
      "warning": counts.get("warning", 0),
      "info": counts.get("info", 0),
    }


def _top_rule(result: AuditResult) -> tuple[str, int]:
    counter = Counter(finding.rule_id for finding in result.findings if finding.rule_id)
    if not counter:
        return "无", 0
    rule_id, count = counter.most_common(1)[0]
    return rule_id, count


def _line_range(result: AuditResult) -> str:
    indices = sorted(
        {
            int(finding.location.paragraph_index) + 1
            for finding in result.findings
            if finding.location.paragraph_index is not None
        }
    )
    if not indices:
        return "未定位"
    if len(indices) == 1:
        return f"第 {indices[0]} 行"
    return f"第 {indices[0]} 行 - 第 {indices[-1]} 行"


def _entity_summary(result: AuditResult) -> str:
    expected = ["title", "doc_number", "recipient", "date"]
    missing = [key for key in expected if not result.entities.get(key)]
    if not missing:
        return "核心实体齐全"
    if len(missing) == len(expected):
        return "标题 / 发文"
    return "缺少 " + " / ".join(missing[:2])


def _format_location(paragraph_index: int | None) -> str:
    if paragraph_index is None:
        return "未定位"
    return f"第 {int(paragraph_index) + 1} 行"
