from __future__ import annotations

from pathlib import Path

from docx import Document
from docx.enum.section import WD_SECTION_START
from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT, WD_TABLE_ALIGNMENT
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
from docx.shared import Inches, Pt, RGBColor


ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "docs" / "市民请集合AI第一期方案汇总.docx"

BLUE = "1E5B8C"
DARK_BLUE = "123B5D"
INK = "0B2545"
MUTED = "667085"
LIGHT_FILL = "EAF1F7"
CALLOUT_FILL = "EEF5FA"
CAUTION_FILL = "FFF8E8"
BORDER = "D0D5DD"
WHITE = "FFFFFF"


def qn_attr(name: str) -> str:
    return qn(name)


def set_cell_shading(cell, fill: str) -> None:
    tc_pr = cell._tc.get_or_add_tcPr()
    shd = tc_pr.find(qn_attr("w:shd"))
    if shd is None:
        shd = OxmlElement("w:shd")
        tc_pr.append(shd)
    shd.set(qn_attr("w:fill"), fill)


def set_cell_margins(cell, top=100, bottom=100, start=140, end=140) -> None:
    tc_pr = cell._tc.get_or_add_tcPr()
    tc_mar = tc_pr.first_child_found_in("w:tcMar")
    if tc_mar is None:
        tc_mar = OxmlElement("w:tcMar")
        tc_pr.append(tc_mar)
    for edge, value in (("top", top), ("bottom", bottom), ("start", start), ("end", end)):
        node = tc_mar.find(qn_attr(f"w:{edge}"))
        if node is None:
            node = OxmlElement(f"w:{edge}")
            tc_mar.append(node)
        node.set(qn_attr("w:w"), str(value))
        node.set(qn_attr("w:type"), "dxa")


def set_table_borders(table, color=BORDER) -> None:
    tbl_pr = table._tbl.tblPr
    borders = tbl_pr.first_child_found_in("w:tblBorders")
    if borders is None:
        borders = OxmlElement("w:tblBorders")
        tbl_pr.append(borders)
    for edge in ("top", "left", "bottom", "right", "insideH", "insideV"):
        node = borders.find(qn_attr(f"w:{edge}"))
        if node is None:
            node = OxmlElement(f"w:{edge}")
            borders.append(node)
        node.set(qn_attr("w:val"), "single")
        node.set(qn_attr("w:sz"), "6")
        node.set(qn_attr("w:space"), "0")
        node.set(qn_attr("w:color"), color)


def set_table_width(table, widths_dxa: list[int]) -> None:
    tbl_pr = table._tbl.tblPr
    tbl_w = tbl_pr.find(qn_attr("w:tblW"))
    if tbl_w is None:
        tbl_w = OxmlElement("w:tblW")
        tbl_pr.append(tbl_w)
    tbl_w.set(qn_attr("w:w"), str(sum(widths_dxa)))
    tbl_w.set(qn_attr("w:type"), "dxa")

    tbl_ind = tbl_pr.find(qn_attr("w:tblInd"))
    if tbl_ind is None:
        tbl_ind = OxmlElement("w:tblInd")
        tbl_pr.append(tbl_ind)
    tbl_ind.set(qn_attr("w:w"), "120")
    tbl_ind.set(qn_attr("w:type"), "dxa")

    grid = table._tbl.tblGrid
    if grid is None:
        grid = OxmlElement("w:tblGrid")
        table._tbl.insert(0, grid)
    for child in list(grid):
        grid.remove(child)
    for width in widths_dxa:
        col = OxmlElement("w:gridCol")
        col.set(qn_attr("w:w"), str(width))
        grid.append(col)

    for row in table.rows:
        for cell, width in zip(row.cells, widths_dxa):
            tc_pr = cell._tc.get_or_add_tcPr()
            tc_w = tc_pr.find(qn_attr("w:tcW"))
            if tc_w is None:
                tc_w = OxmlElement("w:tcW")
                tc_pr.append(tc_w)
            tc_w.set(qn_attr("w:w"), str(width))
            tc_w.set(qn_attr("w:type"), "dxa")
            cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
            set_cell_margins(cell)


def set_run_font(run, font_name="Calibri", east_asia="Microsoft YaHei") -> None:
    run.font.name = font_name
    run._element.rPr.rFonts.set(qn_attr("w:ascii"), font_name)
    run._element.rPr.rFonts.set(qn_attr("w:hAnsi"), font_name)
    run._element.rPr.rFonts.set(qn_attr("w:eastAsia"), east_asia)


def set_paragraph_border_bottom(paragraph, color=BORDER, size="8") -> None:
    p_pr = paragraph._p.get_or_add_pPr()
    p_bdr = p_pr.find(qn_attr("w:pBdr"))
    if p_bdr is None:
        p_bdr = OxmlElement("w:pBdr")
        p_pr.append(p_bdr)
    bottom = p_bdr.find(qn_attr("w:bottom"))
    if bottom is None:
        bottom = OxmlElement("w:bottom")
        p_bdr.append(bottom)
    bottom.set(qn_attr("w:val"), "single")
    bottom.set(qn_attr("w:sz"), size)
    bottom.set(qn_attr("w:space"), "6")
    bottom.set(qn_attr("w:color"), color)


def add_para(
    doc,
    text: str,
    style: str | None = None,
    bold_prefix: str | None = None,
    color: str | None = None,
    italic: bool = False,
):
    p = doc.add_paragraph(style=style)
    if bold_prefix and text.startswith(bold_prefix):
        r = p.add_run(bold_prefix)
        r.bold = True
        if color:
            r.font.color.rgb = RGBColor.from_string(color)
        set_run_font(r)
        tail = p.add_run(text[len(bold_prefix):])
        tail.italic = italic
        if color:
            tail.font.color.rgb = RGBColor.from_string(color)
        set_run_font(tail)
    else:
        r = p.add_run(text)
        r.italic = italic
        if color:
            r.font.color.rgb = RGBColor.from_string(color)
        set_run_font(r)
    return p


def add_bullet(doc, text: str):
    p = doc.add_paragraph(style="List Bullet")
    r = p.add_run(text)
    set_run_font(r)
    return p


def add_number(doc, text: str):
    p = doc.add_paragraph(style="List Number")
    r = p.add_run(text)
    set_run_font(r)
    return p


def add_heading(doc, text: str, level: int):
    p = doc.add_heading(text, level=level)
    for run in p.runs:
        set_run_font(run)
    return p


def add_callout(doc, title: str, body: str, fill: str = CALLOUT_FILL) -> None:
    table = doc.add_table(rows=1, cols=1)
    table.alignment = WD_TABLE_ALIGNMENT.LEFT
    table.style = "Table Grid"
    set_table_width(table, [9360])
    set_table_borders(table)
    cell = table.cell(0, 0)
    set_cell_shading(cell, fill)
    p = cell.paragraphs[0]
    p.paragraph_format.space_after = Pt(4)
    r = p.add_run(title)
    r.bold = True
    r.font.color.rgb = RGBColor.from_string(DARK_BLUE)
    set_run_font(r)
    p2 = cell.add_paragraph()
    p2.paragraph_format.space_after = Pt(0)
    r2 = p2.add_run(body)
    set_run_font(r2)
    doc.add_paragraph()


def add_table(doc, headers: list[str], rows: list[list[str]], widths_dxa: list[int]):
    table = doc.add_table(rows=1, cols=len(headers))
    table.alignment = WD_TABLE_ALIGNMENT.LEFT
    table.style = "Table Grid"
    set_table_width(table, widths_dxa)
    set_table_borders(table)

    for i, text in enumerate(headers):
        cell = table.rows[0].cells[i]
        set_cell_shading(cell, DARK_BLUE)
        p = cell.paragraphs[0]
        p.alignment = WD_ALIGN_PARAGRAPH.CENTER
        p.paragraph_format.space_after = Pt(0)
        r = p.add_run(text)
        r.bold = True
        r.font.color.rgb = RGBColor.from_string(WHITE)
        set_run_font(r)

    for row in rows:
        cells = table.add_row().cells
        for i, text in enumerate(row):
            p = cells[i].paragraphs[0]
            p.alignment = WD_ALIGN_PARAGRAPH.LEFT
            p.paragraph_format.space_after = Pt(0)
            r = p.add_run(text)
            set_run_font(r)
    set_table_width(table, widths_dxa)
    doc.add_paragraph()
    return table


def add_metadata_rows(doc, rows: list[tuple[str, str]]) -> None:
    for label, value in rows:
        p = doc.add_paragraph()
        p.paragraph_format.space_before = Pt(0)
        p.paragraph_format.space_after = Pt(2)
        p.paragraph_format.line_spacing = 1.10
        r1 = p.add_run(label)
        r1.bold = True
        set_run_font(r1)
        r2 = p.add_run(value)
        set_run_font(r2)


def configure_styles(doc: Document) -> None:
    section = doc.sections[0]
    section.page_width = Inches(8.5)
    section.page_height = Inches(11)
    for margin in ("top_margin", "bottom_margin", "left_margin", "right_margin"):
        setattr(section, margin, Inches(1))
    section.header_distance = Inches(0.492)
    section.footer_distance = Inches(0.492)

    styles = doc.styles
    normal = styles["Normal"]
    normal.font.name = "Calibri"
    normal._element.rPr.rFonts.set(qn_attr("w:eastAsia"), "Microsoft YaHei")
    normal.font.size = Pt(11)
    normal.paragraph_format.space_before = Pt(0)
    normal.paragraph_format.space_after = Pt(6)
    normal.paragraph_format.line_spacing = 1.10

    title = styles["Title"]
    title.font.name = "Calibri"
    title._element.rPr.rFonts.set(qn_attr("w:eastAsia"), "Microsoft YaHei")
    title.font.size = Pt(24)
    title.font.bold = True
    title.font.color.rgb = RGBColor.from_string(INK)
    title.paragraph_format.space_before = Pt(0)
    title.paragraph_format.space_after = Pt(8)

    subtitle = styles["Subtitle"]
    subtitle.font.name = "Calibri"
    subtitle._element.rPr.rFonts.set(qn_attr("w:eastAsia"), "Microsoft YaHei")
    subtitle.font.size = Pt(11)
    subtitle.font.color.rgb = RGBColor.from_string(MUTED)
    subtitle.paragraph_format.space_after = Pt(14)

    for name, size, color, before, after in [
        ("Heading 1", 16, BLUE, 16, 8),
        ("Heading 2", 13, BLUE, 12, 6),
        ("Heading 3", 12, DARK_BLUE, 8, 4),
    ]:
        style = styles[name]
        style.font.name = "Calibri"
        style._element.rPr.rFonts.set(qn_attr("w:eastAsia"), "Microsoft YaHei")
        style.font.size = Pt(size)
        style.font.bold = True
        style.font.color.rgb = RGBColor.from_string(color)
        style.paragraph_format.space_before = Pt(before)
        style.paragraph_format.space_after = Pt(after)

    for name in ("List Bullet", "List Number"):
        style = styles[name]
        style.font.name = "Calibri"
        style._element.rPr.rFonts.set(qn_attr("w:eastAsia"), "Microsoft YaHei")
        style.font.size = Pt(11)
        style.paragraph_format.space_after = Pt(8)
        style.paragraph_format.line_spacing = 1.167

    footer = section.footer.paragraphs[0]
    footer.alignment = WD_ALIGN_PARAGRAPH.RIGHT
    r = footer.add_run("市民请集合 AI 第一期方案汇总")
    r.font.size = Pt(9)
    r.font.color.rgb = RGBColor.from_string(MUTED)
    set_run_font(r)


def build_doc() -> None:
    doc = Document()
    configure_styles(doc)

    doc.add_paragraph()
    p = doc.add_paragraph(style="Title")
    r = p.add_run("市民请集合 AI 第一期方案汇总")
    set_run_font(r)
    p = doc.add_paragraph(style="Subtitle")
    r = p.add_run("基于当前 smqjh-agent 系统、第一期业务需求及 4 个 ChatGPT Pro 账号预算整理")
    set_run_font(r)
    add_metadata_rows(
        doc,
        [
            ("版本日期：", "2026-06-30"),
            ("一期预算口径：", "4 个 ChatGPT Pro 账号，USD 200/账号/月，合计 USD 800/月；采购与发票口径同步确认"),
            ("资料依据：", "《市民请集合AI第一期需求.docx》、当前 smqjh-agent 代码与设计复盘文档、OpenAI 官方订阅说明（2026-06-30 查询）"),
        ],
    )
    rule = doc.add_paragraph()
    set_paragraph_border_bottom(rule)

    add_callout(
        doc,
        "结论先行",
        "第一期建议定位为“业务运营 AI 助手试点”：先用当前已具备的 Agent/MCP/只读数据库能力，把查询、报价、对账、充值文件整理等高频人工工作做成可追踪、可导出的辅助流程；ChatGPT 账号用于人员日常方案、表格、文档和复杂问题处理，系统侧继续使用受控 MCP 工具承接内部数据能力。整体不建议一开始承诺全自动写入业务系统，所有涉及金额、上下架、充值、开票和结算确认的动作都保留人工复核。"
    )

    add_heading(doc, "1. 解决什么问题", 1)
    add_para(
        doc,
        "第一期主要解决运营、商务和财务在报价、商品、充值、对账、查询中的重复性整理和跨系统核对问题。当前痛点不是单点功能缺失，而是信息分散在报价单、供应商资料、后台系统、订单/积分/商品表和人工沟通中，导致每次都要重新查、重新改表、重新核口径。",
    )
    add_table(
        doc,
        ["问题类型", "当前表现", "AI 一期要解决的点"],
        [
            ["报价单", "新企业签约时需要基于星闪豹商品、京东旗舰店原价和企业反馈反复调整报价。", "生成固定格式报价单，辅助补齐商品、规格、价格依据，支持对方新增商品后的快速报价和汇总下载。"],
            ["商品上下架", "最终报价单确定后，需要在海博和市民请集合系统做批量或零散上下架。", "解析报价单，生成待上架/下架清单、字段校验和执行建议；高风险写入动作人工确认后再执行。"],
            ["商品名称对应", "平台商品名、供应商开票名称和采购单名称不一致，建采购单耗时。", "建立名称/规格/编码匹配表，上传供应商开票图片或清单后给出候选匹配，人工确认后沉淀映射。"],
            ["积分充值", "企业给的积分导入名单格式不统一，导入后还要输出成功证明，错误反馈需要排查。", "自动清洗为系统导入格式，按规则检查风险，生成导入文件和成功反馈文件。"],
            ["对账结算", "积分过期后需要多方核对、生成对账单、开票明细、盖章和付款跟踪。", "打包对账单和核对结果，计算开票金额/税额关系，标记异常差异，形成可给企业确认的文件包。"],
            ["数据查询", "运营需要按企业、时间、状态查询积分充值/消耗/可用、正常订单和售后订单。", "管理员用自然语言查询，系统自动查只读数据库并输出表格、摘要和依据。"],
        ],
        [1500, 3900, 3960],
    )

    add_para(
        doc,
        "从系统建设角度看，第一期还要解决两个底层问题：一是把业务查询从“人找字段、人写 SQL、人导表”改成“Agent 调受控工具”；二是把 AI 的输出从长文本变成表格、文件、待确认项和可审计日志。",
    )

    add_heading(doc, "2. 能够解决到什么程度", 1)
    add_para(
        doc,
        "当前 smqjh-agent 已有可用底座：Python + LangGraph Web Agent、Java MCP Server、DeepSeek 工具规划、业务 schema、只读数据库查询、商品/订单/月结工具、表格渲染和 CSV 下载。基于这个基础，一期能做到“辅助闭环 + 人工确认”，不宜直接承诺无人值守的全自动业务变更。",
    )
    add_table(
        doc,
        ["场景", "一期可交付程度", "预计自动化/减负", "需要人工确认的边界"],
        [
            ["数据查询", "自然语言查询商品、订单、会员、积分、月结相关数据，并输出表格与依据。", "高，常规查询约 80%-90% 可自动完成。", "字段口径不明确、数据异常、权限不足时确认。"],
            ["报价单", "生成统一报价单模板，辅助商品匹配、价格引用、对方新增品报价和汇总下载。", "中高，格式整理和候选匹配约 60%-70% 可自动化。", "最终报价、利润率、外部平台最新价必须人工确认。"],
            ["对账结算", "按企业和月份生成导出计划、核对规则、差异清单，进一步可生成对账文件包。", "中高，模板确认后约 70%-80% 工作量可减少。", "差异、税率、开票金额、盖章和付款状态确认。"],
            ["积分充值", "清洗企业名单，校验字段、金额、手机号和重复项，生成导入格式和反馈文件。", "中，约 50%-70% 的整理排错可自动化。", "真实导入、批量发放、金额异常必须确认。"],
            ["商品上下架", "从报价单生成上下架清单和字段校验，后续对接接口后进入确认执行。", "中，前处理约 50%-60% 可自动化。", "自动写入海博/市民请集合系统前必须审批。"],
            ["名称/发票匹配", "建立供应商名称、平台商品、SKU、编码映射候选，支持图片/OCR 后续接入。", "中，样本积累前约 50%-60%；字典完善后可提升。", "同规格多候选、开票名称不规范、编码不唯一。"],
        ],
        [1300, 3450, 1850, 2760],
    )

    add_callout(
        doc,
        "一期能力边界",
        "AI 可以先把“查、算、整、比、导、提示风险”做起来；凡是会改变业务系统状态的动作，例如上架、下架、充值导入、发券、结算确认、开票提交，都必须保留人工确认和日志记录。",
        fill=CAUTION_FILL,
    )

    add_heading(doc, "3. 大致时间周期", 1)
    add_para(
        doc,
        "建议一期按 4-6 周推进：4 周形成可演示和小范围试用版本，6 周完成重点场景试运行。账号采购申请和发票确认不等待研发完成，采用同步推进。",
    )
    add_table(
        doc,
        ["阶段", "周期", "主要工作", "交付结果"],
        [
            ["启动与确认", "第 1 周", "确认报价单模板、对账模板、充值导入模板、上下架字段、企业样本和账号采购申请。", "一期范围确认表、样表清单、账号申请单。"],
            ["查询与报价 MVP", "第 2 周", "强化智能查询、商品/订单/积分表口径，完成报价单模板生成和新增品候选匹配。", "自然语言查询演示、报价单生成样例。"],
            ["对账与充值流程", "第 3-4 周", "打通月结企业、订单/积分/运费导出规则、充值文件清洗和风险提示。", "对账文件包样例、充值导入前检查表。"],
            ["上下架与名称匹配试点", "第 5 周", "根据报价单生成上下架清单，沉淀商品名、开票名、SKU 编码映射。", "上下架待确认清单、名称匹配表。"],
            ["试运行与培训", "第 6 周", "用真实业务样本试跑，修正提示词、口径、模板和人工确认流程。", "试运行报告、使用说明、下一期清单。"],
        ],
        [1450, 1200, 4300, 2410],
    )

    add_para(
        doc,
        "如果只做“账号采购 + 文档/表格辅助”可以更快，约 1 周即可启用；如果要把报价、对账、充值、上下架真正沉淀到当前系统里，建议按 4-6 周评估。",
        bold_prefix="补充说明：",
    )

    add_heading(doc, "4. 费用的具体数量", 1)
    add_para(
        doc,
        "本次费用按 ChatGPT Pro 账号采购口径测算：4 个账号，每个账号 USD 200/月，合计 USD 800/月。该口径对应一个月试点费用，不与其他订阅档位混合测算。",
    )
    add_table(
        doc,
        ["费用项", "数量/单价", "金额", "说明"],
        [
            ["ChatGPT Pro 账号", "4 个账号 x USD 200/月", "USD 800/月", "用于业务人员日常方案、表格、文档、复杂问题分析和高强度 AI 使用。"],
            ["采购申请金额", "按 1 个月试点", "USD 800", "先按一个月费用提交申请，后续是否续费根据试运行效果决定。"],
            ["OpenAI API 或其他第三方接口", "未纳入本次 USD 800/月", "另行测算", "ChatGPT Pro 订阅不等同于 API 用量；当前系统侧主要通过 MCP/DeepSeek 跑业务工具。"],
            ["内部系统开发/实施", "使用现有 smqjh-agent 底座推进", "如内部研发，不另列外采费用", "若后续外包 OCR、价格接口或专门爬取服务，需要单独报价。"],
        ],
        [2200, 2450, 1850, 2860],
    )

    add_callout(
        doc,
        "采购与发票说明",
        "ChatGPT 自助订阅通常可在账户账单里下载 invoice/receipt，但这不等同于国内增值税专票或普票。当前建议先按 USD 800/月提交采购申请，同步让财务确认境外订阅 invoice、付款截图、信用卡流水等材料是否可作为报销/入账依据；如果必须取得国内发票，需要另查代理采购或企业合同路径，预算和周期都会变化。",
        fill=CAUTION_FILL,
    )

    add_heading(doc, "5. 推荐推进方式", 1)
    add_number(doc, "先提交 USD 800/月 / 4 个 ChatGPT Pro 账号的试点申请。")
    add_number(doc, "账号采购和系统建设同步走，避免等采购流程导致研发试点停滞。")
    add_number(doc, "第一条端到端样例建议选择“报价单 + 对账查询”组合：展示价值直接，也最贴合当前需求文档。")
    add_number(doc, "所有涉及金额、上下架、充值、开票的结果都输出“待确认清单”，由业务人员确认后再执行。")
    add_number(doc, "试运行结束后，用真实样本统计节省时间、错误率、人工确认比例，再决定第二期是否做更深的自动写入和审批流。")

    add_heading(doc, "6. 可直接用于内部汇报的版本", 1)
    add_callout(
        doc,
        "汇报口径",
        "第一期主要解决报价、商品、充值、对账和查询这几类重复性工作。当前系统已经有 Agent、MCP、只读数据库和表格输出底座，所以不需要从零开始。账号侧先采购 4 个 ChatGPT Pro 账号做业务和文档辅助，预算按 USD 800/月申请；系统侧同步把查询、报价单、对账和充值整理做成可复用流程。预计 4 周能出可演示版本，6 周进入小范围试运行。ChatGPT 自助订阅暂时无法提供国内增值税发票，我们先提交申请，同时让财务确认境外 invoice/付款凭证是否可接受；如果必须走国内发票，再调整采购路径和预算。"
    )

    add_heading(doc, "参考信息", 1)
    add_bullet(doc, "OpenAI Help Center：ChatGPT Pro 档位包含 USD 100/月和 USD 200/月两类，Pro USD 200 对应最高用量档。https://help.openai.com/en/articles/9793128-about-chatgpt-pro-tiers")
    add_bullet(doc, "OpenAI Help Center：自助 ChatGPT 客户可在账单入口查看或下载过往 invoices；如果组织要求 invoice billing，需要联系 Sales 讨论 Enterprise/Education 等合同方案。https://help.openai.com/en/articles/12356340-how-can-i-find-my-past-chatgpt-invoices")
    add_bullet(doc, "当前项目资料：smqjh-agent-runtime、smqjh-mcp-server、smqjh-admin-agent 以及《市民请集合AI第一期需求.docx》。")

    OUT.parent.mkdir(parents=True, exist_ok=True)
    doc.save(OUT)
    print(OUT)


if __name__ == "__main__":
    build_doc()
