"""
skills/pdf_skill.py - PDF 文档生成技能
========================================
基于 ReportLab 生成专业 PDF 文档，融入 MiniMax PDF Skill 设计理念。
支持封面、多级标题、段落、列表、表格、图片、分页等。
"""
from __future__ import annotations

import json
import os
import textwrap
import time
from pathlib import Path
from typing import Any, Dict, List, Optional

from aiskills.base import Skill, SkillParameter, SkillResult
from core.logger import get_logger

logger = get_logger("myagent.pdf_skill")

# ── 调色板 (源自 MiniMax PDF Skill design system) ──
PALETTES = {
    "report":     {"primary": "#1a365d", "secondary": "#2b6cb0", "accent": "#e53e3e",
                   "bg": "#ffffff", "text": "#1a202c", "muted": "#718096"},
    "proposal":   {"primary": "#22543d", "secondary": "#38a169", "accent": "#d69e2e",
                   "bg": "#ffffff", "text": "#1a202c", "muted": "#718096"},
    "academic":   {"primary": "#553c9a", "secondary": "#805ad5", "accent": "#e53e3e",
                   "bg": "#ffffff", "text": "#1a202c", "muted": "#718096"},
    "minimal":    {"primary": "#2d3748", "secondary": "#4a5568", "accent": "#3182ce",
                   "bg": "#ffffff", "text": "#1a202c", "muted": "#718096"},
    "dark":       {"primary": "#e2e8f0", "secondary": "#a0aec0", "accent": "#63b3ed",
                   "bg": "#1a202c", "text": "#e2e8f0", "muted": "#a0aec0"},
    "warm":       {"primary": "#744210", "secondary": "#c05621", "accent": "#d69e2e",
                   "bg": "#fffff0", "text": "#1a202c", "muted": "#718096"},
}


def _hex_to_rgb(h: str) -> tuple:
    h = h.lstrip("#")
    return tuple(int(h[i:i+2], 16) / 255.0 for i in (0, 2, 4))


class PDFCreateSkill(Skill):
    """生成 PDF 文档 — 支持封面、标题、段落、列表、表格、图片

    content 为 JSON 数组，每个元素代表一个内容块:
    - {"type": "h1", "text": "标题"}        — 一级标题
    - {"type": "h2", "text": "标题"}        — 二级标题
    - {"type": "h3", "text": "标题"}        — 三级标题
    - {"type": "body", "text": "正文"}      — 正文段落
    - {"type": "bullet", "items": ["a","b"]} — 无序列表
    - {"type": "numbered", "items": ["a","b"]} — 有序列表
    - {"type": "table", "headers": ["A","B"], "rows": [["1","2"]]}  — 表格
    - {"type": "image", "path": "/path/to/img.png", "width": 400}  — 图片
    - {"type": "divider"}                   — 分割线
    - {"type": "pagebreak"}                 — 分页
    - {"type": "spacer", "height": 20}     — 空白
    - {"type": "callout", "text": "提示", "style": "info"} — 提示框
    """
    name = "pdf_create"
    description = (
        "生成 PDF 文档。支持封面、多级标题、段落、列表、表格、图片、提示框等。"
        "content 为 JSON 数组，每项: {type, text/items/headers/rows/path...}。"
        "type 可选: h1/h2/h3/body/bullet/numbered/table/image/divider/pagebreak/spacer/callout。"
        "palette 可选: report/proposal/academic/minimal/dark/warm。"
    )
    category = "pdf"
    dangerous = True
    parameters = [
        SkillParameter("content", "string",
            "PDF 内容块 JSON 数组，每项含 type 字段。示例: [{\"type\":\"h1\",\"text\":\"报告标题\"},{\"type\":\"body\",\"text\":\"正文内容\"}]",
            required=True),
        SkillParameter("output_path", "string",
            "输出 PDF 文件路径 (如 /tmp/report.pdf)", required=True),
        SkillParameter("title", "string", "文档标题 (用于封面)", required=False, default=""),
        SkillParameter("subtitle", "string", "副标题 (用于封面)", required=False, default=""),
        SkillParameter("author", "string", "作者", required=False, default=""),
        SkillParameter("palette", "string", "配色方案", required=False,
                       default="report", enum=["report","proposal","academic","minimal","dark","warm"]),
        SkillParameter("cover", "boolean", "是否生成封面", required=False, default=True),
        SkillParameter("page_size", "string", "页面尺寸", required=False,
                       default="A4", enum=["A4","Letter"]),
    ]

    async def execute(self, content: str = "", output_path: str = "",
                      title: str = "", subtitle: str = "", author: str = "",
                      palette: str = "report", cover: bool = True,
                      page_size: str = "A4", **kwargs) -> SkillResult:
        try:
            from reportlab.lib.pagesizes import A4, letter
            from reportlab.lib.units import mm, cm
            from reportlab.lib.colors import HexColor
            from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
            from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_JUSTIFY
            from reportlab.platypus import (SimpleDocTemplate, Paragraph, Spacer,
                Table, TableStyle, Image as RLImage, PageBreak, HRFlowable, KeepTogether)
            from reportlab.pdfbase import pdfmetrics
            from reportlab.pdfbase.ttfonts import TTFont
        except ImportError:
            return SkillResult(success=False, error="ReportLab 未安装，请运行: pip install reportlab")

        # 解析 content（兼容 LLM 输出尾部多余字符的情况）
        try:
            if isinstance(content, str):
                content = content.strip()
                # 先尝试标准解析
                try:
                    blocks = json.loads(content)
                except json.JSONDecodeError:
                    # 再用 raw_decode 只取第一个完整 JSON 值，忽略尾部多余数据
                    decoder = json.JSONDecoder()
                    blocks, _ = decoder.raw_decode(content)
            else:
                blocks = content
            if not isinstance(blocks, list):
                blocks = [{"type": "body", "text": str(blocks)}]
        except (json.JSONDecodeError, ValueError) as e:
            return SkillResult(success=False, error=f"content JSON 解析失败: {e}")

        try:
            # 输出路径
            if not output_path.strip():
                default_dir = Path.home() / ".myagent" / "data" / "workspace" / "userfiles"
                default_dir.mkdir(parents=True, exist_ok=True)
                out = default_dir / f"doc_{int(time.time())}.pdf"
            else:
                out = Path(output_path).expanduser().resolve()
            out.parent.mkdir(parents=True, exist_ok=True)

            # 配色
            pal = PALETTES.get(palette, PALETTES["report"])

            # 页面尺寸
            ps = A4 if page_size.upper() == "A4" else letter
            pw, ph = ps

            # 注册中文字体 — 优先级：SarasaMonoSC > PingFangSC > WQYZenHei > LXGWWenKai > NotoSerifSC
            _zh_fonts = {}
            import platform as _platform
            _is_macos = _platform.system() == "Darwin"
            _is_windows = _platform.system() == "Windows"

            # 构建字体候选列表：按平台添加不同路径
            _font_candidates = []

            if _is_macos:
                # ── macOS 中文字体 ──
                # PingFang SC (苹方) — macOS 系统自带，覆盖最全
                _font_candidates += [
                    ("/System/Library/Fonts/PingFang.ttc", "PingFangSC", 0),        # Regular
                    ("/System/Library/Fonts/PingFang.ttc", "PingFangSC-Semibold", 1), # Semibold
                    # STHeiti (华文黑体)
                    ("/System/Library/Fonts/STHeiti Light.ttc", "STHeitiLight", 0),
                    ("/System/Library/Fonts/STHeiti Medium.ttc", "STHeitiMedium", 0),
                    # Songti SC (宋体)
                    ("/Library/Fonts/Songti.ttc", "SongtiSC", 0),
                    # Hiragino Sans GB
                    ("/System/Library/Fonts/Hiragino Sans GB.ttc", "HiraginoSansGB", 0),
                ]
                # macOS 英文字体
                _font_candidates += [
                    ("/System/Library/Fonts/Helvetica.ttc", "HelveticaNative", 0),
                    ("/Library/Fonts/Arial.ttf", "ArialNative", None),
                ]
            elif _is_windows:
                # ── Windows 中文字体 ──
                _windir = os.environ.get("WINDIR", "C:\\Windows")
                _font_candidates += [
                    (os.path.join(_windir, "Fonts", "msyh.ttc"), "MicrosoftYaHei", 0),
                    (os.path.join(_windir, "Fonts", "msyhbd.ttc"), "MicrosoftYaHei-Bold", 0),
                    (os.path.join(_windir, "Fonts", "simhei.ttf"), "SimHei", None),
                    (os.path.join(_windir, "Fonts", "simsun.ttc"), "SimSun", 0),
                ]

            # ── Linux 中文字体（通用路径 /usr/share/fonts/） ──
            _font_candidates += [
                # (路径, 字体名, subfontIndex) — .ttc 文件需要 subfontIndex
                # [v1.39] 修复PDF乱码: 移除不存在的 SimHei 和损坏的 NotoSansSC variable font
                # 添加 SarasaMonoSC (等宽CJK)、LiberationSerif (英文衬线)、Carlito (英文无衬线)
                ("/usr/share/fonts/truetype/chinese/SarasaMonoSC-Regular.ttf", "SarasaMonoSC", None),
                ("/usr/share/fonts/truetype/chinese/SarasaMonoSC-Bold.ttf", "SarasaMonoSC-Bold", None),
                ("/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc", "WQYZenHei", 0),
                ("/usr/share/fonts/truetype/lxgw-wenkai/LXGWWenKai-Regular.ttf", "LXGWWenKai", None),
                ("/usr/share/fonts/truetype/noto-serif-sc/NotoSerifSC-Regular.ttf", "NotoSerifSC", None),
                ("/usr/share/fonts/truetype/noto-serif-sc/NotoSerifSC-Bold.ttf", "NotoSerifSC-Bold", None),
                ("/usr/share/fonts/truetype/chinese/LiberationSerif-Regular.ttf", "LiberationSerif", None),
                ("/usr/share/fonts/truetype/chinese/LiberationSans-Regular.ttf", "LiberationSans", None),
                ("/usr/share/fonts/truetype/english/Carlito-Regular.ttf", "Carlito", None),
                ("/usr/share/fonts/truetype/english/Carlito-Bold.ttf", "Carlito-Bold", None),
                ("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", "DejaVuSans", None),
            ]

            # [v1.40] 额外扫描常见字体目录（覆盖 conda/homebrew 等非标准安装）
            _extra_dirs = []
            if _is_macos:
                _extra_dirs = ["/opt/homebrew/share/fonts", "/usr/local/share/fonts"]
            elif _is_windows:
                _extra_dirs = []
            else:
                _extra_dirs = ["/usr/local/share/fonts", os.path.expanduser("~/.fonts"), os.path.expanduser("~/.local/share/fonts")]
            for _edir in _extra_dirs:
                if not os.path.isdir(_edir):
                    continue
                for _root, _dirs, _files in os.walk(_edir):
                    for _fn in _files:
                        _fl = _fn.lower()
                        if _fl.endswith(('.ttf', '.ttc')) and any(kw in _fl for kw in ('sarasa', 'noto', 'wqy', 'lxgw', 'wenkai', 'pingfang', 'yahei', 'simhei', 'simsun', 'heiti', 'songti', 'cjk', 'chinese', 'hans')):
                            _fpath = os.path.join(_root, _fn)
                            _fname = os.path.splitext(_fn)[0].replace(' ', '').replace('-', '')
                            _subfont = 0 if _fl.endswith('.ttc') else None
                            _font_candidates.append((_fpath, _fname, _subfont))
            for _fpath, _fname, _subfont_idx in _font_candidates:
                if os.path.isfile(_fpath):
                    try:
                        if _subfont_idx is not None:
                            # .ttc (TrueType Collection) 需要 subfontIndex 参数
                            pdfmetrics.registerFont(TTFont(_fname, _fpath, subfontIndex=_subfont_idx))
                        else:
                            pdfmetrics.registerFont(TTFont(_fname, _fpath))
                        _zh_fonts[_fname] = _fpath
                        logger.debug(f"PDF中文字体注册成功: {_fname} ({_fpath})")
                    except Exception as _e:
                        logger.warning(f"PDF中文字体注册失败: {_fname} ({_fpath}): {_e}")

            # 选择第一个成功注册的中文字体，若全部失败则回退 Helvetica
            # [v1.40] 增加 macOS/Windows 字体优先级
            for _preferred in ["PingFangSC", "SarasaMonoSC", "MicrosoftYaHei", "WQYZenHei", "LXGWWenKai", "NotoSerifSC", "STHeitiLight", "HiraginoSansGB", "SimHei", "SongtiSC"]:
                if _preferred in _zh_fonts:
                    font_name = _preferred
                    break
            else:
                font_name = "Helvetica"
                logger.warning("PDF: 所有中文字体注册失败，将使用 Helvetica（中文内容将显示为乱码或方框）")

            # 样式
            styles = getSampleStyleSheet()
            primary_rgb = _hex_to_rgb(pal["primary"])
            secondary_rgb = _hex_to_rgb(pal["secondary"])
            accent_rgb = _hex_to_rgb(pal["accent"])
            text_rgb = _hex_to_rgb(pal["text"])
            muted_rgb = _hex_to_rgb(pal["muted"])

            styles.add(ParagraphStyle(
                "zh_h1", fontName=font_name, fontSize=22, leading=28,
                textColor=HexColor(pal["primary"]), spaceAfter=12, spaceBefore=20))
            styles.add(ParagraphStyle(
                "zh_h2", fontName=font_name, fontSize=16, leading=22,
                textColor=HexColor(pal["primary"]), spaceAfter=8, spaceBefore=16))
            styles.add(ParagraphStyle(
                "zh_h3", fontName=font_name, fontSize=13, leading=18,
                textColor=HexColor(pal["secondary"]), spaceAfter=6, spaceBefore=12))
            styles.add(ParagraphStyle(
                "zh_body", fontName=font_name, fontSize=10.5, leading=17,
                textColor=HexColor(pal["text"]), alignment=TA_JUSTIFY,
                spaceAfter=6, firstLineIndent=21))
            styles.add(ParagraphStyle(
                "zh_bullet", fontName=font_name, fontSize=10.5, leading=17,
                textColor=HexColor(pal["text"]), leftIndent=20, bulletIndent=8,
                spaceAfter=3))
            styles.add(ParagraphStyle(
                "zh_numbered", fontName=font_name, fontSize=10.5, leading=17,
                textColor=HexColor(pal["text"]), leftIndent=20, bulletIndent=8,
                spaceAfter=3))
            styles.add(ParagraphStyle(
                "zh_table_header", fontName=font_name, fontSize=10,
                textColor=HexColor("#ffffff"), alignment=TA_CENTER))
            styles.add(ParagraphStyle(
                "zh_table_cell", fontName=font_name, fontSize=9.5,
                textColor=HexColor(pal["text"]), alignment=TA_LEFT))
            styles.add(ParagraphStyle(
                "zh_callout", fontName=font_name, fontSize=10, leading=16,
                textColor=HexColor(pal["text"]), leftIndent=12, rightIndent=12,
                spaceBefore=8, spaceAfter=8, backColor=HexColor("#ebf8ff"),
                borderColor=HexColor(pal["secondary"]), borderWidth=1,
                borderPadding=8))

            # 构建文档元素
            story = []

            # 封面
            if cover and title:
                story.append(Spacer(1, ph * 0.3))
                _accent = HexColor(pal["accent"])
                story.append(HRFlowable(width="40%", thickness=3, color=_accent,
                                         spaceAfter=20, spaceBefore=0, hAlign="CENTER"))
                _t = ParagraphStyle("cover_title", fontName=font_name, fontSize=28,
                                    leading=36, textColor=HexColor(pal["primary"]),
                                    alignment=TA_CENTER, spaceAfter=12)
                story.append(Paragraph(title, _t))
                if subtitle:
                    _s = ParagraphStyle("cover_sub", fontName=font_name, fontSize=14,
                                        leading=20, textColor=HexColor(pal["secondary"]),
                                        alignment=TA_CENTER, spaceAfter=8)
                    story.append(Paragraph(subtitle, _s))
                if author:
                    _a = ParagraphStyle("cover_author", fontName=font_name, fontSize=11,
                                        leading=16, textColor=HexColor(pal["muted"]),
                                        alignment=TA_CENTER, spaceAfter=6)
                    story.append(Paragraph(author, _a))
                story.append(HRFlowable(width="40%", thickness=3, color=_accent,
                                         spaceAfter=0, spaceBefore=20, hAlign="CENTER"))
                story.append(PageBreak())

            # 内容块
            for block in blocks:
                if not isinstance(block, dict):
                    continue
                bt = block.get("type", "body")

                if bt == "h1":
                    story.append(Paragraph(str(block.get("text", "")), styles["zh_h1"]))
                elif bt == "h2":
                    story.append(Paragraph(str(block.get("text", "")), styles["zh_h2"]))
                elif bt == "h3":
                    story.append(Paragraph(str(block.get("text", "")), styles["zh_h3"]))
                elif bt == "body":
                    story.append(Paragraph(str(block.get("text", "")), styles["zh_body"]))
                elif bt == "bullet":
                    items = block.get("items", [])
                    for item in items:
                        story.append(Paragraph(f"\u2022  {item}", styles["zh_bullet"]))
                elif bt == "numbered":
                    items = block.get("items", [])
                    for i, item in enumerate(items, 1):
                        story.append(Paragraph(f"{i}.  {item}", styles["zh_numbered"]))
                elif bt == "table":
                    headers = block.get("headers", [])
                    rows = block.get("rows", [])
                    if headers:
                        data = []
                        hdr_row = [Paragraph(str(h), styles["zh_table_header"]) for h in headers]
                        data.append(hdr_row)
                        for row in rows:
                            data.append([Paragraph(str(c), styles["zh_table_cell"]) for c in row])
                        col_w = (pw - 80) / max(len(headers), 1)
                        t = Table(data, colWidths=[col_w] * len(headers))
                        t.setStyle(TableStyle([
                            ("BACKGROUND", (0, 0), (-1, 0), HexColor(pal["primary"])),
                            ("TEXTCOLOR", (0, 0), (-1, 0), HexColor("#ffffff")),
                            ("ALIGN", (0, 0), (-1, 0), "CENTER"),
                            ("FONTNAME", (0, 0), (-1, 0), font_name),
                            ("FONTSIZE", (0, 0), (-1, 0), 10),
                            ("BOTTOMPADDING", (0, 0), (-1, 0), 8),
                            ("TOPPADDING", (0, 0), (-1, 0), 8),
                            ("BACKGROUND", (0, 1), (-1, -1), HexColor("#f7fafc")),
                            ("TEXTCOLOR", (0, 1), (-1, -1), HexColor(pal["text"])),
                            ("FONTNAME", (0, 1), (-1, -1), font_name),
                            ("FONTSIZE", (0, 1), (-1, -1), 9.5),
                            ("ALIGN", (0, 1), (-1, -1), "LEFT"),
                            ("BOTTOMPADDING", (0, 1), (-1, -1), 6),
                            ("TOPPADDING", (0, 1), (-1, -1), 6),
                            ("GRID", (0, 0), (-1, -1), 0.5, HexColor("#cbd5e0")),
                            ("ROWBACKGROUNDS", (0, 1), (-1, -1),
                             [HexColor("#ffffff"), HexColor("#f7fafc")]),
                        ]))
                        story.append(t)
                elif bt == "image":
                    img_path = block.get("path", "")
                    img_w = block.get("width", pw - 80)
                    if img_path and os.path.isfile(img_path):
                        try:
                            img = RLImage(img_path, width=img_w, height=img_w * 0.75)
                            img.hAlign = "CENTER"
                            story.append(img)
                        except Exception as e:
                            story.append(Paragraph(f"[图片加载失败: {e}]", styles["zh_body"]))
                elif bt == "divider":
                    story.append(HRFlowable(width="100%", thickness=0.5,
                                             color=HexColor("#cbd5e0"), spaceAfter=8, spaceBefore=8))
                elif bt == "pagebreak":
                    story.append(PageBreak())
                elif bt == "spacer":
                    story.append(Spacer(1, block.get("height", 20)))
                elif bt == "callout":
                    style_map = {"info": "#ebf8ff", "warning": "#fffbeb", "error": "#fff5f5",
                                 "success": "#f0fff4"}
                    bg = style_map.get(block.get("style", "info"), "#ebf8ff")
                    cs = ParagraphStyle("callout_dyn", fontName=font_name, fontSize=10,
                                        leading=16, textColor=HexColor(pal["text"]),
                                        leftIndent=12, rightIndent=12,
                                        spaceBefore=8, spaceAfter=8,
                                        backColor=HexColor(bg), borderWidth=1,
                                        borderColor=HexColor(pal["secondary"]),
                                        borderPadding=8)
                    story.append(Paragraph(str(block.get("text", "")), cs))

            # 生成 PDF
            doc = SimpleDocTemplate(str(out), pagesize=ps,
                                     leftMargin=30, rightMargin=30,
                                     topMargin=30, bottomMargin=30)
            doc.build(story)

            return SkillResult(
                success=True,
                message=f"PDF 文档已生成: {out}",
                files=[str(out)],
                data={"path": str(out), "pages": "?", "blocks": len(blocks)},
        )
        except Exception as e:
            return SkillResult(success=False, error=f"PDF 生成失败: {e}")


class PDFReadSkill(Skill):
    """读取 PDF 文件内容，提取文本"""
    name = "pdf_read"
    description = "读取 PDF 文件，提取文本内容。支持提取指定页码范围。"
    category = "pdf"
    parameters = [
        SkillParameter("path", "string", "PDF 文件路径", required=True),
        SkillParameter("page_start", "integer", "起始页 (从1开始)", required=False, default=1),
        SkillParameter("page_end", "integer", "结束页 (0=全部)", required=False, default=0),
        SkillParameter("max_chars", "integer", "最大字符数", required=False, default=50000),
    ]

    async def execute(self, path: str = "", page_start: int = 1, page_end: int = 0,
                      max_chars: int = 50000, **kwargs) -> SkillResult:
        try:
            import importlib
            for mod_name in ["pypdf", "PyPDF2"]:
                try:
                    mod = importlib.import_module(mod_name)
                    break
                except ImportError:
                    continue
            else:
                return SkillResult(success=False, error="需要 pypdf 或 PyPDF2: pip install pypdf")

            fp = Path(path).expanduser().resolve()
            if not fp.exists():
                return SkillResult(success=False, error=f"文件不存在: {path}")

            reader = mod.PdfReader(str(fp))
            total = len(reader.pages)
            ps = page_start - 1
            pe = page_end if page_end > 0 else total
            ps = max(0, min(ps, total - 1))
            pe = max(ps, min(pe, total))

            texts = []
            for i in range(ps, pe):
                page = reader.pages[i]
                t = page.extract_text() or ""
                texts.append(f"--- 第 {i+1} 页 ---\n{t}")

            full = "\n\n".join(texts)
            if len(full) > max_chars:
                full = full[:max_chars] + f"\n\n... (截断，共 {len(full)} 字符)"

            return SkillResult(
                success=True,
                message=f"已读取 PDF: {fp.name} (第{ps+1}-{pe}/{total}页)",
                data={"path": str(fp), "total_pages": total, "pages_read": pe - ps},
                output=full,
            )
        except Exception as e:
            return SkillResult(success=False, error=f"PDF 读取失败: {e}")
