from __future__ import annotations

import os
from copy import deepcopy
from pathlib import Path

from docx import Document
from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT, WD_ROW_HEIGHT_RULE, 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 Cm, Pt


SOURCE = Path(
    os.environ.get(
        "CODE_AUDIT_OFFICIAL_TEMPLATE",
        "/Users/wangshuo/Codex/aizhct/work/2026-09-09-mlps-rectification-response/"
        "北京康比特体育科技股份有限公司-网络安全限期整改回函.docx",
    )
)
OUTPUT_DIR = Path(
    os.environ.get(
        "CODE_AUDIT_OUTPUT_DIR",
        str(Path.home() / "Desktop/等保整改证明材料-20260916/01-代码审计"),
    )
)
OUTPUT = OUTPUT_DIR / "01-代码审计落实情况说明-公文版V3.docx"

BODY_FONT = "仿宋_GB2312"
TITLE_FONT = "方正小标宋简体"
HEADING_FONT = "黑体"


def set_east_asia(run, name: str) -> None:
    r_pr = run._element.get_or_add_rPr()
    r_fonts = r_pr.find(qn("w:rFonts"))
    if r_fonts is None:
        r_fonts = OxmlElement("w:rFonts")
        r_pr.insert(0, r_fonts)
    for key in ("ascii", "hAnsi", "eastAsia", "cs"):
        r_fonts.set(qn(f"w:{key}"), name)


def style_run(run, *, font: str = BODY_FONT, size: float = 16, bold: bool = False) -> None:
    run.font.name = font
    run.font.size = Pt(size)
    run.bold = bold
    set_east_asia(run, font)


def clear_body(doc: Document) -> None:
    body = doc._element.body
    sect_pr = body.sectPr
    for child in list(body):
        if child is not sect_pr:
            body.remove(child)


def set_paragraph_format(paragraph, *, first_line=True, align=WD_ALIGN_PARAGRAPH.JUSTIFY,
                         line_pt=28, before=0, after=0) -> None:
    paragraph.alignment = align
    fmt = paragraph.paragraph_format
    fmt.line_spacing = Pt(line_pt)
    fmt.space_before = Pt(before)
    fmt.space_after = Pt(after)
    fmt.first_line_indent = Cm(0.74) if first_line else Cm(0)
    fmt.keep_together = True


def add_text(doc: Document, text: str, *, first_line=True, bold_lead: str | None = None,
             align=WD_ALIGN_PARAGRAPH.JUSTIFY, line_pt=28, before=0, after=0,
             font=BODY_FONT, size=16) -> None:
    p = doc.add_paragraph()
    set_paragraph_format(
        p, first_line=first_line, align=align, line_pt=line_pt, before=before, after=after
    )
    if bold_lead and text.startswith(bold_lead):
        r1 = p.add_run(bold_lead)
        style_run(r1, font=HEADING_FONT, size=size)
        r2 = p.add_run(text[len(bold_lead):])
        style_run(r2, font=font, size=size)
    else:
        r = p.add_run(text)
        style_run(r, font=font, size=size)


def add_title(doc: Document, line1: str, line2: str) -> None:
    p = doc.add_paragraph()
    set_paragraph_format(
        p, first_line=False, align=WD_ALIGN_PARAGRAPH.CENTER, line_pt=31, before=0, after=14
    )
    r = p.add_run(f"{line1}\n{line2}")
    style_run(r, font=TITLE_FONT, size=22)


def add_page_break(doc: Document) -> None:
    p = doc.add_paragraph()
    p.paragraph_format.space_after = Pt(0)
    p.add_run().add_break()
    p.runs[0]._element.get_or_add_rPr()
    br = p.runs[0]._element.find(qn("w:br"))
    br.set(qn("w:type"), "page")


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


def set_cell_margins(cell, top=80, start=90, bottom=80, end=90) -> 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 name, value in (("top", top), ("start", start), ("bottom", bottom), ("end", end)):
        node = tc_mar.find(qn(f"w:{name}"))
        if node is None:
            node = OxmlElement(f"w:{name}")
            tc_mar.append(node)
        node.set(qn("w:w"), str(value))
        node.set(qn("w:type"), "dxa")


def set_repeat_header(row) -> None:
    tr_pr = row._tr.get_or_add_trPr()
    el = OxmlElement("w:tblHeader")
    el.set(qn("w:val"), "true")
    tr_pr.append(el)


def prevent_row_split(row) -> None:
    tr_pr = row._tr.get_or_add_trPr()
    el = OxmlElement("w:cantSplit")
    el.set(qn("w:val"), "true")
    tr_pr.append(el)


def set_table_borders(table, color="000000", size="8") -> 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"):
        elem = borders.find(qn(f"w:{edge}"))
        if elem is None:
            elem = OxmlElement(f"w:{edge}")
            borders.append(elem)
        elem.set(qn("w:val"), "single")
        elem.set(qn("w:sz"), size)
        elem.set(qn("w:color"), color)


def add_table(doc: Document, headers: list[str], rows: list[list[str]], widths: list[float],
              font_size=10.5) -> None:
    table = doc.add_table(rows=1, cols=len(headers))
    table.alignment = WD_TABLE_ALIGNMENT.CENTER
    table.autofit = False
    set_table_borders(table)
    for i, (cell, text) in enumerate(zip(table.rows[0].cells, headers)):
        cell.width = Cm(widths[i])
        cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
        set_cell_shading(cell, "E7E6E6")
        set_cell_margins(cell)
        p = cell.paragraphs[0]
        p.alignment = WD_ALIGN_PARAGRAPH.CENTER
        p.paragraph_format.space_after = Pt(0)
        p.paragraph_format.line_spacing = Pt(16)
        r = p.add_run(text)
        style_run(r, font=HEADING_FONT, size=font_size)
    set_repeat_header(table.rows[0])
    for row_data in rows:
        row = table.add_row()
        row.height_rule = WD_ROW_HEIGHT_RULE.AT_LEAST
        prevent_row_split(row)
        for i, (cell, text) in enumerate(zip(row.cells, row_data)):
            cell.width = Cm(widths[i])
            cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
            set_cell_margins(cell)
            p = cell.paragraphs[0]
            p.alignment = WD_ALIGN_PARAGRAPH.CENTER if i == 0 else WD_ALIGN_PARAGRAPH.LEFT
            p.paragraph_format.space_after = Pt(0)
            p.paragraph_format.line_spacing = Pt(16)
            r = p.add_run(text)
            style_run(r, size=font_size)


def configure_document(doc: Document) -> None:
    section = doc.sections[0]
    section.page_width = Cm(21)
    section.page_height = Cm(29.7)
    section.top_margin = Cm(2.4)
    section.bottom_margin = Cm(2.3)
    section.left_margin = Cm(2.7)
    section.right_margin = Cm(2.7)
    section.header_distance = Cm(1.5)
    section.footer_distance = Cm(1.5)
    core = doc.core_properties
    core.title = "智慧营养健康餐厅管理系统代码审计落实情况说明"
    core.subject = "网络安全限期整改证明材料"
    core.author = "北京康比特体育科技股份有限公司"
    core.keywords = "代码审计, 网络安全, 限期整改"


def build() -> None:
    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
    doc = Document(SOURCE)
    clear_body(doc)
    configure_document(doc)

    add_title(doc, "北京康比特体育科技股份有限公司", "关于代码审计工作落实情况的说明")
    add_text(doc, "北京市公安局昌平分局网安部门：", first_line=False, align=WD_ALIGN_PARAGRAPH.LEFT)
    add_text(
        doc,
        "《北京市公安局昌平分局网络安全监督检查限期整改通知书》"
        "（昌平公网安限字〔2026〕第0087号）收悉。针对通知书指出的智慧营养健康餐厅管理系统上线前未开展代码审计问题，我单位已组织开展代码审查和整改核验，现将有关情况说明如下：",
    )
    add_text(
        doc,
        "一、已开展的工作。以系统指定源代码快照为对象，对登录与会话、人员信息接口、访问控制、输入校验、敏感信息保护和生产配置等重点环节开展有限范围人工辅助静态检查；同步核对既有修复提交、定向测试记录和服务器目标文件，形成检查记录和问题清单。",
        bold_lead="一、已开展的工作。",
    )
    add_text(
        doc,
        "二、制度和职责落实。现有《安全开发管理规定》《组织及岗位职责管理规定》《变更管理规定》《授权和审批管理规定》已明确安全需求、代码检查、测试、变更、审批和上线前评审要求。我单位据此将发现登记、整改、复核及记录留存纳入开发、测试和运维环节，相关制度用于证明管理要求，不代替本次实际检查记录。",
        bold_lead="二、制度和职责落实。",
    )
    add_text(
        doc,
        "三、当前结果。截至2026年9月10日，已完成指定快照的首轮重点检查，共形成6类待整改事项，涉及多因素认证状态、对象级授权、动态查询、敏感配置、会话令牌保护和口令存储等方面。历史人员详情接口授权问题已有修复提交和定向测试记录，服务器目标文件与本地修复文件哈希一致；但受限账号线上合法对象、越权对象专项复测尚未完成，其余发现也尚未形成关闭证据。",
        bold_lead="三、当前结果。",
    )
    add_text(
        doc,
        "四、后续安排。我单位将继续按照问题清单逐项整改和复核，补充修复提交、测试结果、上线版本映射及审批记录，并于2026年9月16日前提交本阶段证明材料。未完成闭环的事项持续跟踪并据实补充，不以本次有限范围检查替代第三方独立审计或生产全系统验证。",
        bold_lead="四、后续安排。",
    )
    add_text(doc, "附件：1. 代码审计范围及结果说明\n　　　2. 关键整改和核验材料摘录", first_line=False)
    add_text(doc, "北京康比特体育科技股份有限公司\n（盖章）", first_line=False,
             align=WD_ALIGN_PARAGRAPH.RIGHT, line_pt=25, before=8)
    add_text(doc, "2026年9月10日", first_line=False, align=WD_ALIGN_PARAGRAPH.RIGHT, line_pt=25)

    add_page_break(doc)
    add_title(doc, "附件1", "代码审计范围及结果说明")
    add_text(
        doc,
        "审查对象为智慧营养健康餐厅管理系统指定源代码快照，分支codex/csfzx-staff-info-horizontal-auth-20260824，提交号3428f1522d9b7c1a3dfed1c372963e0e15c25e44。审查方式为有限范围人工辅助静态检查，并核对历史专项测试和服务器文件哈希。",
        line_pt=25,
        size=14,
    )
    add_table(
        doc,
        ["序号", "重点问题", "截至2026年9月10日状态"],
        [
            ["1", "多因素认证状态由客户端Cookie参与判定", "待整改、待复核"],
            ["2", "人员接口对象级授权校验不完整", "历史详情接口子问题已修复；其余待整改"],
            ["3", "日期配餐查询存在动态SQL拼接", "待整改、待复核"],
            ["4", "版本库中存在明文敏感配置风险", "待整改、待复核；本材料不展示敏感值"],
            ["5", "会话令牌保护及账号状态复核不足", "待整改、待复核"],
            ["6", "口令摘要算法和盐值强度不足", "待整改、待复核"],
        ],
        [1.2, 8.2, 6.2],
        font_size=11,
    )
    add_text(
        doc,
        "说明：上述状态以现有代码、测试和服务器回读证据为准；没有关闭证据的事项不作“已完成整改”结论。",
        line_pt=23,
        size=13,
        before=8,
    )

    add_page_break(doc)
    add_title(doc, "附件2", "关键整改和核验材料摘录")
    add_table(
        doc,
        ["材料", "关键内容", "证明边界"],
        [
            ["历史修复提交", "人员详情接口修复提交为3428f1522d9b7c1a3dfed1c372963e0e15c25e44。", "证明该历史子问题已有代码修复"],
            ["定向测试记录", "历史记录显示3项测试、10项断言通过。", "仅覆盖人员详情接口定向回归；同批全量测试并非全部通过"],
            ["服务器文件核验", "线上与本地Staff.php文件SHA-256均为5cc6b23a99a3990f94c236b7d99378194c61a48057b101c460b55a5f5c22368c。", "仅证明该目标文件内容一致，不证明整站Git版本一致"],
            ["尚待补充", "受限账号线上合法对象、越权对象专项复测；其余5类发现的修复、测试、复核及上线映射证据。", "补齐前不作全量关闭结论"],
        ],
        [3.0, 8.3, 4.3],
        font_size=10.5,
    )
    add_text(
        doc,
        "制度依据摘录：现有安全开发、岗位职责、变更及授权审批制度对代码检查、问题整改、测试复核、上线评审和记录留存作出管理要求。本附件只摘录与本次工作直接相关的证明点，详细技术记录和敏感配置留存内部备查。",
        line_pt=25,
        size=14,
        before=10,
    )
    add_text(
        doc,
        "以上材料均依据截至2026年9月10日可回读记录整理，不包含印章、个人签名或第三方审计结论。",
        line_pt=25,
        size=14,
    )

    doc.save(OUTPUT)
    print(OUTPUT)


if __name__ == "__main__":
    build()
