# -*- coding: utf-8 -*-
"""
xlsx_to_md.py — Excel 问题反馈跟踪表 → Markdown 知识库转换工具

功能：
  读取 ONLYOFFICE 导出的 xlsx 文件，将每条问题记录拆分为独立的 Markdown 文件。

  工作表映射：
    - 日常问题记录 → Docs/PRD/问题知识库/问题记录/日常/
    - 项目问题记录 → Docs/PRD/问题知识库/问题记录/项目/
    - 旧版本问题反馈表 → Docs/PRD/问题知识库/问题记录/归档/

用法：
  python xlsx_to_md.py [xlsx文件路径]

  不指定文件时，默认读取 Downloads 目录下最新的"问题反馈跟踪表"。
"""

import os
import re
import sys
import json
import shutil
from pathlib import Path
from datetime import datetime

import openpyxl

# ============================================================
# 配置
# ============================================================

# 项目根目录（从脚本位置向上推导）
SCRIPT_DIR = Path(__file__).resolve().parent
PROJECT_ROOT = SCRIPT_DIR.parent.parent.parent.parent  # .claude/skills/Troubleshoot/code → 项目根

# 输出目录
OUTPUT_DIR = PROJECT_ROOT / "Docs" / "PRD" / "问题知识库" / "问题记录"
# 数据文件目录
DATA_DIR = PROJECT_ROOT / "Docs" / "PRD" / "问题知识库"

# 支持的工作表名
SHEET_MAP = {
    "日常问题记录": "日常",
    "项目问题记录": "项目",
    "（新问题不要记录到此处）旧版本问题反馈表": "归档",
}

# 列标题 → Markdown 字段映射（按原 Excel 列顺序）
COLUMN_MAP_DAILY = {
    "序号": "id",
    "出处": "source_detail",
    "现象": "phenomenon",
    "记录人": "recorder",
    "问题产生时间": "date",
    "现场造成的影响": "impact",
    "产生问题的原因": "root_cause",
    "排查流程": "troubleshoot_steps",
    "如何解决": "solution",
    "日志是否能马上定位到、是否已经补充(再次发现如何定位到)": "log_traceable",
    "图片": "images",
    "责任人": "responsible",
    "怎么进一步优化": "prevention",
    "是否已解决": "resolved",
    "是否关闭": "closed",
}

COLUMN_MAP_PROJECT = {
    "项目": "project",
    "现象": "phenomenon",
    "记录人": "recorder",
    "问题产生时间": "date",
    "现场造成的影响": "impact",
    "产生问题的原因": "root_cause",
    "排查流程": "troubleshoot_steps",
    "如何解决": "solution",
    "日志是否能马上定位到、是否已经补充(再次发现如何定位到)": "log_traceable",
    "图片": "images",
    "责任人": "responsible",
    "怎么进一步优化": "prevention",
    "是否已解决": "resolved",
    "关闭问题": "closed",
}


def find_latest_xlsx():
    """在 Downloads 目录查找最新的问题反馈跟踪表"""
    downloads = Path.home() / "Downloads"
    candidates = list(downloads.glob("问题反馈跟踪表*.xlsx"))
    if not candidates:
        return None
    return max(candidates, key=lambda p: p.stat().st_mtime)


def sanitize_filename(text):
    """从文本中提取安全的文件名片段（取前 20 个有效字符）"""
    if not text:
        return "未知"
    # 去除不可打印字符和特殊符号
    text = re.sub(r'[\\/:*?"<>|\r\n\t]', '', str(text))
    # 压缩多余空格
    text = re.sub(r'\s+', ' ', text).strip()
    if len(text) > 20:
        text = text[:20]
    return text if text else "未知"


def extract_keywords(text):
    """从文本中提取关键词（用于文件名和标签）"""
    if not text:
        return []
    text = str(text).lower()
    keywords = []
    # 常见技术关键词
    tech_keywords = [
        "redis", "mysql", "nacos", "emqx", "mqtt", "docker",
        "nginx", "nacos", "java", "python", "smc", "exchange",
        "门口屏", "无纸化", "桌牌", "人脸", "签到", "白名单",
        "wss", "websocket", "授权", "激活", "token", "证书",
        "时区", "ntp", "openssh", "磁盘", "内存", "oom",
        "配置", "config", "短信", "邮件", "同步", "飞书",
        "钉钉", "企业微信", "welink", "oa", "exchange",
        "500", "502", "404", "连接失败", "启动失败", "未授权",
    ]
    for kw in tech_keywords:
        if kw in text:
            keywords.append(kw)
    return keywords[:8]  # 最多 8 个关键词


def build_filename(seq, project, phenomenon, max_len=60):
    """构建文件名：序号-项目名-关键词摘要.md"""
    parts = []
    if seq is not None:
        parts.append(str(seq).zfill(3))
    if project:
        proj = sanitize_filename(project)
        if proj and proj != "未知":
            parts.append(proj)
    if phenomenon:
        phenom = sanitize_filename(phenomenon)
        if phenom and phenom != "未知":
            parts.append(phenom)

    filename = "-".join(parts)
    if len(filename) > max_len:
        # 截断到 max_len，保持完整字符
        filename = filename[:max_len].rsplit("-", 1)[0]

    return filename + ".md"


def categorize(text):
    """根据问题描述自动归类"""
    if not text:
        return []
    text = str(text)
    categories = []
    rules = {
        "服务/容器异常": ["docker", "容器", "服务", "500", "502", "启动失败", "exited", "退出"],
        "设备终端-门口屏": ["门口屏", "门ロ屏"],
        "设备终端-无纸化": ["无纸化"],
        "设备终端-桌牌": ["桌牌", "电子桌牌"],
        "MQTT/EMQX": ["mqtt", "emqx", "wss", "websocket"],
        "数据库": ["mysql", "redis", "数据库", "连接失败", "connection refused"],
        "配置文件": ["配置", "config", "不生效"],
        "前端/UI": ["页面", "前端", "ui", "显示", "浏览器"],
        "部署/升级": ["部署", "升级", "更新", "安装"],
        "网络/SSH": ["网络", "ssh", "端口", "连通", "不通"],
        "时区/时间": ["时区", "时间", "8小时"],
        "第三方对接": ["smc", "exchange", "腾讯", "钉钉", "华为", "飞书"],
        "授权/激活": ["授权", "激活", "token", "jwt", "证书"],
        "人脸/签到": ["人脸", "签到", "白名单"],
        "数据同步": ["同步", "welink", "企业微信", "oa"],
        "安全漏洞": ["安全", "漏洞", "等保", "弱密码"],
        "版本兼容": ["版本", "兼容", "不兼容"],
        "邮件/短信": ["邮件", "短信", "通知"],
        "磁盘/资源": ["磁盘", "内存", "oom", "cpu"],
    }
    for cat, keywords in rules.items():
        for kw in keywords:
            if kw in text:
                categories.append(cat)
                break
    return categories if categories else ["其他"]


def parse_resolved(value):
    """解析'是否已解决'字段为 status"""
    if not value:
        return "未知"
    v = str(value).strip()
    if v in ("是", "已解决", "已关闭", "yes", "YES", "Y", "1", "True", "true", "√", "✓"):
        return "已解决"
    if v in ("否", "未解决", "no", "NO", "N", "0", "False", "false", "×", "✗"):
        return "未解决"
    return v  # 保留原值


def parse_date(value):
    """解析日期字段"""
    if not value:
        return ""
    if isinstance(value, datetime):
        return value.strftime("%Y-%m-%d")
    return str(value).strip()


def generate_frontmatter(record):
    """生成 Markdown frontmatter"""
    fm = {
        "id": record.get("id", ""),
        "date": record.get("date", ""),
        "source": record.get("_source", ""),
        "project": record.get("project", ""),
        "category": record.get("_categories", []),
        "status": record.get("resolved", "未知"),
        "closed": record.get("closed", ""),
        "keywords": record.get("_keywords", []),
        "recorder": record.get("recorder", ""),
        "responsible": record.get("responsible", ""),
    }

    lines = ["---"]
    for key, value in fm.items():
        if key == "category" or key == "keywords":
            if value:
                lines.append(f"{key}: [{', '.join(str(v) for v in value if v)}]")
            else:
                lines.append(f"{key}: []")
        elif isinstance(value, str):
            # 转义字符串中的双引号
            escaped = value.replace('"', '\\"').replace('\n', ' ')
            lines.append(f'{key}: "{escaped}"')
        else:
            lines.append(f"{key}: {value}")
    lines.append("---")
    return "\n".join(lines)


def generate_markdown(record):
    """根据记录生成完整的 Markdown 内容"""
    title = record.get("phenomenon", "未知问题")
    if record.get("project"):
        title = f"[{record['project']}] {title}"

    sections = []
    sections.append(f"# {title}")
    sections.append("")
    sections.append("## 现象")
    sections.append(str(record.get("phenomenon", "（待补充）")))
    sections.append("")

    if record.get("impact"):
        sections.append("## 影响")
        sections.append(str(record["impact"]))
        sections.append("")

    if record.get("root_cause"):
        sections.append("## 根因")
        sections.append(str(record["root_cause"]))
        sections.append("")

    if record.get("troubleshoot_steps"):
        sections.append("## 排查流程")
        sections.append(str(record["troubleshoot_steps"]))
        sections.append("")

    if record.get("solution"):
        sections.append("## 解决方案")
        sections.append(str(record["solution"]))
        sections.append("")

    if record.get("prevention"):
        sections.append("## 预防措施")
        sections.append(str(record["prevention"]))
        sections.append("")

    sections.append("## 元信息")
    sections.append(f"- 记录人：{record.get('recorder', '未知')}")
    sections.append(f"- 责任人：{record.get('responsible', '未知')}")
    sections.append(f"- 日志可定位：{record.get('log_traceable', '未知')}")
    sections.append(f"- 是否已解决：{record.get('resolved', '未知')}")
    if record.get("images"):
        sections.append(f"- 截图：{record['images']}")

    frontmatter = generate_frontmatter(record)
    return frontmatter + "\n\n" + "\n".join(sections)


def process_sheet(ws, sheet_type, output_subdir):
    """处理一个工作表，生成 Markdown 文件"""
    # 读取列标题（第一行）
    headers = []
    for cell in ws[1]:
        headers.append(str(cell.value) if cell.value else "")

    # 确定列映射
    if sheet_type == "项目":
        col_map = COLUMN_MAP_PROJECT
    else:
        col_map = COLUMN_MAP_DAILY

    # 建立列索引映射
    col_index = {}
    for i, header in enumerate(headers):
        if header in col_map:
            col_index[col_map[header]] = i

    # 确保输出目录存在
    output_dir = OUTPUT_DIR / output_subdir
    output_dir.mkdir(parents=True, exist_ok=True)

    count = 0
    for row_idx, row in enumerate(ws.iter_rows(min_row=2, values_only=True), start=2):
        # 跳过完全空行
        if all(cell is None or str(cell).strip() == '' for cell in row):
            continue

        # 构建记录字典
        record = {"_source": sheet_type}
        for field, idx in col_index.items():
            if idx < len(row) and row[idx] is not None:
                record[field] = str(row[idx]).strip() if not isinstance(row[idx], datetime) else row[idx]
            else:
                record[field] = ""

        # 跳过没有现象描述的空记录（仅有序号或项目名）
        if not record.get("phenomenon"):
            continue

        # 处理日期
        record["date"] = parse_date(record.get("date", ""))

        # 处理后状态
        record["resolved"] = parse_resolved(record.get("resolved", ""))

        # 自动提取关键词
        phenomenon = record.get("phenomenon", "")
        full_text = phenomenon + " " + record.get("root_cause", "") + " " + record.get("solution", "")
        record["_keywords"] = extract_keywords(full_text)

        # 自动归类
        record["_categories"] = categorize(full_text) or categorize(phenomenon)

        # 生成文件名
        seq = record.get("id", row_idx - 1) if record.get("id") else row_idx - 1
        project = record.get("project", "")
        filename = build_filename(seq, project, phenomenon)
        filepath = output_dir / filename

        # 如果文件已存在（序号冲突），追加时间戳
        if filepath.exists():
            filename = filename.replace(".md", f"-{row_idx}.md")
            filepath = output_dir / filename

        # 生成 Markdown 并写入
        md_content = generate_markdown(record)
        filepath.write_text(md_content, encoding="utf-8")

        count += 1
        if count % 50 == 0:
            print(f"  已处理 {count} 条...")

    return count


def main():
    """主函数：解析 xlsx → 生成 Markdown 知识库"""
    # 设置控制台编码
    import sys
    if hasattr(sys.stdout, 'reconfigure'):
        sys.stdout.reconfigure(encoding='utf-8')

    # 确定输入文件
    if len(sys.argv) > 1:
        xlsx_path = Path(sys.argv[1])
    else:
        xlsx_path = find_latest_xlsx()
        if not xlsx_path:
            print("❌ 未找到问题反馈跟踪表.xlsx，请指定文件路径：")
            print("   python xlsx_to_md.py <xlsx文件路径>")
            sys.exit(1)

    if not xlsx_path.exists():
        print(f"❌ 文件不存在：{xlsx_path}")
        sys.exit(1)

    print(f"📂 读取文件：{xlsx_path.name} ({xlsx_path.stat().st_size / 1024 / 1024:.1f} MB)")

    # 清理旧的输出目录
    if OUTPUT_DIR.exists():
        print(f"🗑️  清理旧文件：{OUTPUT_DIR}")
        shutil.rmtree(OUTPUT_DIR)

    # 打开 Excel
    wb = openpyxl.load_workbook(str(xlsx_path), read_only=True, data_only=True)
    print(f"📊 工作表列表：{wb.sheetnames}")

    total = 0
    stats = {}

    for sheet_name, subdir in SHEET_MAP.items():
        if sheet_name not in wb.sheetnames:
            print(f"⚠️  工作表 '{sheet_name}' 不存在，跳过")
            continue

        ws = wb[sheet_name]
        count = process_sheet(ws, subdir, subdir)
        stats[subdir] = count
        total += count
        print(f"  ✅ {subdir}：{count} 条记录")

    wb.close()

    # 写入统计信息
    stats_file = DATA_DIR / "转换统计.json"
    stats_data = {
        "last_convert": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
        "source_file": xlsx_path.name,
        "total": total,
        "breakdown": stats,
    }
    stats_file.write_text(json.dumps(stats_data, ensure_ascii=False, indent=2), encoding="utf-8")

    print(f"\n🎉 转换完成！总计 {total} 条记录")
    print(f"   输出目录：{OUTPUT_DIR}")
    print(f"   统计文件：{stats_file}")


if __name__ == "__main__":
    main()
